diff --git a/IPython/config/application.py b/IPython/config/application.py deleted file mode 100644 index 8468b33..0000000 --- a/IPython/config/application.py +++ /dev/null @@ -1,520 +0,0 @@ -# encoding: utf-8 -""" -A base class for a configurable application. - -Authors: - -* Brian Granger -* Min RK -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import logging -import os -import re -import sys -from copy import deepcopy -from collections import defaultdict - -from IPython.external.decorator import decorator - -from IPython.config.configurable import SingletonConfigurable -from IPython.config.loader import ( - KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, -) - -from IPython.utils.traitlets import ( - Unicode, List, Enum, Dict, Instance, TraitError -) -from IPython.utils.importstring import import_item -from IPython.utils.text import indent, wrap_paragraphs, dedent - -#----------------------------------------------------------------------------- -# function for re-wrapping a helpstring -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Descriptions for the various sections -#----------------------------------------------------------------------------- - -# merge flags&aliases into options -option_description = """ -Arguments that take values are actually convenience aliases to full -Configurables, whose aliases are listed on the help line. For more information -on full configurables, see '--help-all'. -""".strip() # trim newlines of front and back - -keyvalue_description = """ -Parameters are set from command-line arguments of the form: -`--Class.trait=value`. -This line is evaluated in Python, so simple expressions are allowed, e.g.:: -`--C.a='range(3)'` For setting C.a=[0,1,2]. -""".strip() # trim newlines of front and back - -subcommand_description = """ -Subcommands are launched as `{app} cmd [args]`. For information on using -subcommand 'cmd', do: `{app} cmd -h`. -""".strip().format(app=os.path.basename(sys.argv[0])) -# get running program name - -#----------------------------------------------------------------------------- -# Application class -#----------------------------------------------------------------------------- - -@decorator -def catch_config_error(method, app, *args, **kwargs): - """Method decorator for catching invalid config (Trait/ArgumentErrors) during init. - - On a TraitError (generally caused by bad config), this will print the trait's - message, and exit the app. - - For use on init methods, to prevent invoking excepthook on invalid input. - """ - try: - return method(app, *args, **kwargs) - except (TraitError, ArgumentError) as e: - app.print_description() - app.print_help() - app.print_examples() - app.log.fatal("Bad config encountered during initialization:") - app.log.fatal(str(e)) - app.log.debug("Config at the time: %s", app.config) - app.exit(1) - - -class ApplicationError(Exception): - pass - - -class Application(SingletonConfigurable): - """A singleton application with full configuration support.""" - - # The name of the application, will usually match the name of the command - # line application - name = Unicode(u'application') - - # The description of the application that is printed at the beginning - # of the help. - description = Unicode(u'This is an application.') - # default section descriptions - option_description = Unicode(option_description) - keyvalue_description = Unicode(keyvalue_description) - subcommand_description = Unicode(subcommand_description) - - # The usage and example string that goes at the end of the help string. - examples = Unicode() - - # A sequence of Configurable subclasses whose config=True attributes will - # be exposed at the command line. - classes = List([]) - - # The version string of this application. - version = Unicode(u'0.0') - - # The log level for the application - log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'), - default_value=logging.WARN, - config=True, - help="Set the log level by value or name.") - def _log_level_changed(self, name, old, new): - """Adjust the log level when log_level is set.""" - if isinstance(new, basestring): - new = getattr(logging, new) - self.log_level = new - self.log.setLevel(new) - - log_format = Unicode("[%(name)s] %(message)s", config=True, - help="The Logging format template", - ) - log = Instance(logging.Logger) - def _log_default(self): - """Start logging for this application. - - The default is to log to stdout using a StreaHandler. The log level - starts at loggin.WARN, but this can be adjusted by setting the - ``log_level`` attribute. - """ - log = logging.getLogger(self.__class__.__name__) - log.setLevel(self.log_level) - if sys.executable.endswith('pythonw.exe'): - # this should really go to a file, but file-logging is only - # hooked up in parallel applications - _log_handler = logging.StreamHandler(open(os.devnull, 'w')) - else: - _log_handler = logging.StreamHandler() - _log_formatter = logging.Formatter(self.log_format) - _log_handler.setFormatter(_log_formatter) - log.addHandler(_log_handler) - return log - - # the alias map for configurables - aliases = Dict({'log-level' : 'Application.log_level'}) - - # flags for loading Configurables or store_const style flags - # flags are loaded from this dict by '--key' flags - # 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 - # and the second being the help string for the subcommand - subcommands = Dict() - # parse_command_line will initialize a subapp, if requested - subapp = Instance('IPython.config.application.Application', allow_none=True) - - # extra command-line arguments that don't set config values - extra_args = List(Unicode) - - - def __init__(self, **kwargs): - SingletonConfigurable.__init__(self, **kwargs) - # Ensure my class is in self.classes, so my attributes appear in command line - # options and config files. - if self.__class__ not in self.classes: - self.classes.insert(0, self.__class__) - - def _config_changed(self, name, old, new): - SingletonConfigurable._config_changed(self, name, old, new) - self.log.debug('Config changed:') - self.log.debug(repr(new)) - - @catch_config_error - def initialize(self, argv=None): - """Do the basic steps to configure me. - - Override in subclasses. - """ - self.parse_command_line(argv) - - - def start(self): - """Start the app mainloop. - - Override in subclasses. - """ - if self.subapp is not None: - return self.subapp.start() - - def print_alias_help(self): - """Print the alias part of the help.""" - if not self.aliases: - return - - lines = [] - classdict = {} - for cls in self.classes: - # include all parents (up to, but excluding Configurable) in available names - for c in cls.mro()[:-3]: - classdict[c.__name__] = c - - for alias, longname in self.aliases.iteritems(): - classname, traitname = longname.split('.',1) - cls = classdict[classname] - - trait = cls.class_traits(config=True)[traitname] - help = cls.class_get_trait_help(trait).splitlines() - # reformat first line - help[0] = help[0].replace(longname, alias) + ' (%s)'%longname - if len(alias) == 1: - help[0] = help[0].replace('--%s='%alias, '-%s '%alias) - lines.extend(help) - # lines.append('') - print os.linesep.join(lines) - - def print_flag_help(self): - """Print the flag part of the help.""" - if not self.flags: - return - - lines = [] - for m, (cfg,help) in self.flags.iteritems(): - prefix = '--' if len(m) > 1 else '-' - lines.append(prefix+m) - lines.append(indent(dedent(help.strip()))) - # lines.append('') - print os.linesep.join(lines) - - def print_options(self): - if not self.flags and not self.aliases: - return - lines = ['Options'] - lines.append('-'*len(lines[0])) - lines.append('') - for p in wrap_paragraphs(self.option_description): - lines.append(p) - lines.append('') - print os.linesep.join(lines) - self.print_flag_help() - self.print_alias_help() - print - - def print_subcommands(self): - """Print the subcommand part of the help.""" - if not self.subcommands: - return - - lines = ["Subcommands"] - lines.append('-'*len(lines[0])) - lines.append('') - for p in wrap_paragraphs(self.subcommand_description): - lines.append(p) - lines.append('') - for subc, (cls, help) in self.subcommands.iteritems(): - lines.append(subc) - if help: - lines.append(indent(dedent(help.strip()))) - lines.append('') - print os.linesep.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. - """ - self.print_subcommands() - self.print_options() - - if classes: - if self.classes: - print "Class parameters" - print "----------------" - print - for p in wrap_paragraphs(self.keyvalue_description): - print p - print - - for cls in self.classes: - cls.class_print_help() - print - else: - print "To see all available configurables, use `--help-all`" - print - - def print_description(self): - """Print the application description.""" - for p in wrap_paragraphs(self.description): - print p - print - - def print_examples(self): - """Print usage and examples. - - This usage string goes at the end of the command line help string - and should contain examples of the application's usage. - """ - if self.examples: - print "Examples" - print "--------" - print - print indent(dedent(self.examples.strip())) - print - - def print_version(self): - """Print the version string.""" - print self.version - - def update_config(self, config): - """Fire the traits events when the config is updated.""" - # Save a copy of the current config. - newconfig = deepcopy(self.config) - # Merge the new config into the current one. - newconfig._merge(config) - # Save the combined config as self.config, which triggers the traits - # events. - self.config = newconfig - - @catch_config_error - def initialize_subcommand(self, subc, argv=None): - """Initialize a subcommand with argv.""" - subapp,help = self.subcommands.get(subc) - - if isinstance(subapp, basestring): - subapp = import_item(subapp) - - # clear existing instances - self.__class__.clear_instance() - # instantiate - self.subapp = subapp.instance() - # and initialize subapp - self.subapp.initialize(argv) - - def flatten_flags(self): - """flatten flags and aliases, so cl-args override as expected. - - This prevents issues such as an alias pointing to InteractiveShell, - but a config file setting the same trait in TerminalInteraciveShell - getting inappropriate priority over the command-line arg. - - Only aliases with exactly one descendent in the class list - will be promoted. - - """ - # build a tree of classes in our list that inherit from a particular - # it will be a dict by parent classname of classes in our list - # that are descendents - mro_tree = defaultdict(list) - for cls in self.classes: - clsname = cls.__name__ - for parent in cls.mro()[1:-3]: - # exclude cls itself and Configurable,HasTraits,object - mro_tree[parent.__name__].append(clsname) - # flatten aliases, which have the form: - # { 'alias' : 'Class.trait' } - aliases = {} - for alias, cls_trait in self.aliases.iteritems(): - cls,trait = cls_trait.split('.',1) - children = mro_tree[cls] - if len(children) == 1: - # exactly one descendent, promote alias - cls = children[0] - aliases[alias] = '.'.join([cls,trait]) - - # flatten flags, which are of the form: - # { 'key' : ({'Cls' : {'trait' : value}}, 'help')} - flags = {} - for key, (flagdict, help) in self.flags.iteritems(): - newflag = {} - for cls, subdict in flagdict.iteritems(): - children = mro_tree[cls] - # exactly one descendent, promote flag section - if len(children) == 1: - cls = children[0] - newflag[cls] = subdict - flags[key] = (newflag, help) - return flags, aliases - - @catch_config_error - def parse_command_line(self, argv=None): - """Parse the command line arguments.""" - argv = sys.argv[1:] if argv is None else argv - - if argv and argv[0] == 'help': - # turn `ipython help notebook` into `ipython notebook -h` - argv = argv[1:] + ['-h'] - - if self.subcommands and len(argv) > 0: - # we have subcommands, and one may have been specified - subc, subargv = argv[0], argv[1:] - if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands: - # it's a subcommand, and *not* a flag or class parameter - return self.initialize_subcommand(subc, subargv) - - # Arguments after a '--' argument are for the script IPython may be - # about to run, not IPython iteslf. For arguments parsed here (help and - # version), we want to only search the arguments up to the first - # occurrence of '--', which we're calling interpreted_argv. - try: - interpreted_argv = argv[:argv.index('--')] - except ValueError: - interpreted_argv = argv - - if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')): - self.print_description() - self.print_help('--help-all' in interpreted_argv) - self.print_examples() - self.exit(0) - - if '--version' in interpreted_argv or '-V' in interpreted_argv: - self.print_version() - self.exit(0) - - # flatten flags&aliases, so cl-args get appropriate priority: - flags,aliases = self.flatten_flags() - - loader = KVArgParseConfigLoader(argv=argv, aliases=aliases, - flags=flags) - config = loader.load_config() - self.update_config(config) - # store unparsed args in extra_args - self.extra_args = loader.extra_args - - @catch_config_error - def load_config_file(self, filename, path=None): - """Load a .py based config file by filename and path.""" - loader = PyFileConfigLoader(filename, path=path) - try: - config = loader.load_config() - except ConfigFileNotFound: - # problem finding the file, raise - raise - except Exception: - # try to get the full filename, but it will be empty in the - # unlikely event that the error raised before filefind finished - filename = loader.full_filename or filename - # problem while running the file - self.log.error("Exception while loading config file %s", - filename, exc_info=True) - else: - self.log.debug("Loaded config file: %s", loader.full_filename) - self.update_config(config) - - def generate_config_file(self): - """generate default config file from Configurables""" - lines = ["# Configuration file for %s."%self.name] - lines.append('') - lines.append('c = get_config()') - lines.append('') - for cls in self.classes: - lines.append(cls.class_config_section()) - return '\n'.join(lines) - - def exit(self, exit_status=0): - self.log.debug("Exiting application: %s" % self.name) - sys.exit(exit_status) - -#----------------------------------------------------------------------------- -# utility functions, for convenience -#----------------------------------------------------------------------------- - -def boolean_flag(name, configurable, set_help='', unset_help=''): - """Helper for building basic --trait, --no-trait flags. - - Parameters - ---------- - - name : str - The name of the flag. - configurable : str - The 'Class.trait' string of the trait to be set/unset with the flag - set_help : unicode - help string for --name flag - unset_help : unicode - help string for --no-name flag - - Returns - ------- - - cfg : dict - A dict with two keys: 'name', and 'no-name', for setting and unsetting - the trait, respectively. - """ - # default helpstrings - set_help = set_help or "set %s=True"%configurable - unset_help = unset_help or "set %s=False"%configurable - - cls,trait = configurable.split('.') - - 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 deleted file mode 100644 index 2d3e3f4..0000000 --- a/IPython/config/configurable.py +++ /dev/null @@ -1,356 +0,0 @@ -# encoding: utf-8 -""" -A base class for objects that are configurable. - -Inheritance diagram: - -.. inheritance-diagram:: IPython.config.configurable - :parts: 3 - -Authors: - -* Brian Granger -* Fernando Perez -* Min RK -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import datetime -from copy import deepcopy - -from loader import Config -from IPython.utils.traitlets import HasTraits, Instance -from IPython.utils.text import indent, wrap_paragraphs - - -#----------------------------------------------------------------------------- -# Helper classes for Configurables -#----------------------------------------------------------------------------- - - -class ConfigurableError(Exception): - pass - - -class MultipleInstanceError(ConfigurableError): - pass - -#----------------------------------------------------------------------------- -# Configurable implementation -#----------------------------------------------------------------------------- - -class Configurable(HasTraits): - - config = Instance(Config,(),{}) - created = None - - def __init__(self, **kwargs): - """Create a configurable given a config config. - - Parameters - ---------- - config : Config - If this is empty, default values are used. If config is a - :class:`Config` instance, it will be used to configure the - instance. - - Notes - ----- - Subclasses of Configurable must call the :meth:`__init__` method of - :class:`Configurable` *before* doing anything else and using - :func:`super`:: - - class MyConfigurable(Configurable): - def __init__(self, config=None): - super(MyConfigurable, self).__init__(config) - # Then any other code you need to finish initialization. - - This ensures that instances will be configured properly. - """ - config = kwargs.pop('config', None) - if config is not None: - # We used to deepcopy, but for now we are trying to just save - # by reference. This *could* have side effects as all components - # will share config. In fact, I did find such a side effect in - # _config_changed below. If a config attribute value was a mutable type - # all instances of a component were getting the same copy, effectively - # making that a class attribute. - # self.config = deepcopy(config) - self.config = config - # This should go second so individual keyword arguments override - # the values in config. - super(Configurable, self).__init__(**kwargs) - self.created = datetime.datetime.now() - - #------------------------------------------------------------------------- - # Static trait notifiations - #------------------------------------------------------------------------- - - def _config_changed(self, name, old, new): - """Update all the class traits having ``config=True`` as metadata. - - For any class trait with a ``config`` metadata attribute that is - ``True``, we update the trait with the value of the corresponding - config entry. - """ - # Get all traits with a config metadata entry that is True - traits = self.traits(config=True) - - # We auto-load config section for this class as well as any parent - # classes that are Configurable subclasses. This starts with Configurable - # and works down the mro loading the config for each section. - section_names = [cls.__name__ for cls in \ - reversed(self.__class__.__mro__) if - issubclass(cls, Configurable) and issubclass(self.__class__, cls)] - - for sname in section_names: - # Don't do a blind getattr as that would cause the config to - # dynamically create the section with name self.__class__.__name__. - if new._has_section(sname): - my_config = new[sname] - for k, v in traits.iteritems(): - # Don't allow traitlets with config=True to start with - # uppercase. Otherwise, they are confused with Config - # subsections. But, developers shouldn't have uppercase - # attributes anyways! (PEP 6) - if k[0].upper()==k[0] and not k.startswith('_'): - raise ConfigurableError('Configurable traitlets with ' - 'config=True must start with a lowercase so they are ' - 'not confused with Config subsections: %s.%s' % \ - (self.__class__.__name__, k)) - try: - # Here we grab the value from the config - # If k has the naming convention of a config - # section, it will be auto created. - config_value = my_config[k] - except KeyError: - pass - else: - # print "Setting %s.%s from %s.%s=%r" % \ - # (self.__class__.__name__,k,sname,k,config_value) - # We have to do a deepcopy here if we don't deepcopy the entire - # config object. If we don't, a mutable config_value will be - # shared by all instances, effectively making it a class attribute. - setattr(self, k, deepcopy(config_value)) - - def update_config(self, config): - """Fire the traits events when the config is updated.""" - # Save a copy of the current config. - newconfig = deepcopy(self.config) - # Merge the new config into the current one. - newconfig._merge(config) - # Save the combined config as self.config, which triggers the traits - # events. - self.config = newconfig - - @classmethod - def class_get_help(cls, inst=None): - """Get the help string for this class in ReST format. - - If `inst` is given, it's current trait values will be used in place of - class defaults. - """ - assert inst is None or isinstance(inst, cls) - cls_traits = cls.class_traits(config=True) - final_help = [] - final_help.append(u'%s options' % cls.__name__) - final_help.append(len(final_help[0])*u'-') - for k,v in sorted(cls.class_traits(config=True).iteritems()): - help = cls.class_get_trait_help(v, inst) - final_help.append(help) - return '\n'.join(final_help) - - @classmethod - def class_get_trait_help(cls, trait, inst=None): - """Get the help string for a single trait. - - If `inst` is given, it's current trait values will be used in place of - the class default. - """ - assert inst is None or isinstance(inst, cls) - lines = [] - header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__) - lines.append(header) - if inst is not None: - lines.append(indent('Current: %r' % getattr(inst, trait.name), 4)) - else: - try: - dvr = repr(trait.get_default_value()) - except Exception: - dvr = None # ignore defaults we can't construct - if dvr is not None: - 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: - help = '\n'.join(wrap_paragraphs(help, 76)) - lines.append(indent(help, 4)) - return '\n'.join(lines) - - @classmethod - def class_print_help(cls, inst=None): - """Get the help string for a single trait and print it.""" - print cls.class_get_help(inst) - - @classmethod - def class_config_section(cls): - """Get the config class config section""" - def c(s): - """return a commented, wrapped block.""" - s = '\n\n'.join(wrap_paragraphs(s, 78)) - - return '# ' + s.replace('\n', '\n# ') - - # section header - breaker = '#' + '-'*78 - s = "# %s configuration"%cls.__name__ - lines = [breaker, s, breaker, ''] - # get the description trait - desc = cls.class_traits().get('description') - if desc: - desc = desc.default_value - else: - # no description trait, use __doc__ - desc = getattr(cls, '__doc__', '') - if desc: - lines.append(c(desc)) - lines.append('') - - parents = [] - for parent in cls.mro(): - # only include parents that are not base classes - # and are not the class itself - # and have some configurable traits to inherit - if parent is not cls and issubclass(parent, Configurable) and \ - parent.class_traits(config=True): - parents.append(parent) - - if parents: - pstr = ', '.join([ p.__name__ for p in parents ]) - lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr))) - lines.append('') - - for name,trait in cls.class_traits(config=True).iteritems(): - help = trait.get_metadata('help') or '' - lines.append(c(help)) - lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value())) - lines.append('') - return '\n'.join(lines) - - - -class SingletonConfigurable(Configurable): - """A configurable that only allows one instance. - - This class is for classes that should only have one instance of itself - or *any* subclass. To create and retrieve such a class use the - :meth:`SingletonConfigurable.instance` method. - """ - - _instance = None - - @classmethod - def _walk_mro(cls): - """Walk the cls.mro() for parent classes that are also singletons - - For use in instance() - """ - - for subclass in cls.mro(): - if issubclass(cls, subclass) and \ - issubclass(subclass, SingletonConfigurable) and \ - subclass != SingletonConfigurable: - yield subclass - - @classmethod - def clear_instance(cls): - """unset _instance for this class and singleton parents. - """ - if not cls.initialized(): - return - for subclass in cls._walk_mro(): - if isinstance(subclass._instance, cls): - # only clear instances that are instances - # of the calling class - subclass._instance = None - - @classmethod - def instance(cls, *args, **kwargs): - """Returns a global instance of this class. - - This method create a new instance if none have previously been created - and returns a previously created instance is one already exists. - - The arguments and keyword arguments passed to this method are passed - on to the :meth:`__init__` method of the class upon instantiation. - - Examples - -------- - - Create a singleton class using instance, and retrieve it:: - - >>> from IPython.config.configurable import SingletonConfigurable - >>> class Foo(SingletonConfigurable): pass - >>> foo = Foo.instance() - >>> foo == Foo.instance() - True - - Create a subclass that is retrived using the base class instance:: - - >>> class Bar(SingletonConfigurable): pass - >>> class Bam(Bar): pass - >>> bam = Bam.instance() - >>> bam == Bar.instance() - True - """ - # Create and save the instance - if cls._instance is None: - inst = cls(*args, **kwargs) - # Now make sure that the instance will also be returned by - # parent classes' _instance attribute. - for subclass in cls._walk_mro(): - subclass._instance = inst - - if isinstance(cls._instance, cls): - return cls._instance - else: - raise MultipleInstanceError( - 'Multiple incompatible subclass instances of ' - '%s are being created.' % cls.__name__ - ) - - @classmethod - def initialized(cls): - """Has an instance been created?""" - return hasattr(cls, "_instance") and cls._instance is not None - - -class LoggingConfigurable(Configurable): - """A parent class for Configurables that log. - - Subclasses have a log trait, and the default behavior - is to get the logger from the currently running Application - via Application.instance().log. - """ - - log = Instance('logging.Logger') - def _log_default(self): - from IPython.config.application import Application - return Application.instance().log - - diff --git a/IPython/config/loader.py b/IPython/config/loader.py deleted file mode 100644 index 12cf91f..0000000 --- a/IPython/config/loader.py +++ /dev/null @@ -1,701 +0,0 @@ -"""A simple configuration system. - -Inheritance diagram: - -.. inheritance-diagram:: IPython.config.loader - :parts: 3 - -Authors -------- -* Brian Granger -* Fernando Perez -* Min RK -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import __builtin__ as builtin_mod -import os -import re -import sys - -from IPython.external import argparse -from IPython.utils.path import filefind, get_ipython_dir -from IPython.utils import py3compat, text, warn -from IPython.utils.encoding import DEFAULT_ENCODING - -#----------------------------------------------------------------------------- -# Exceptions -#----------------------------------------------------------------------------- - - -class ConfigError(Exception): - pass - -class ConfigLoaderError(ConfigError): - pass - -class ConfigFileNotFound(ConfigError): - pass - -class ArgumentError(ConfigLoaderError): - pass - -#----------------------------------------------------------------------------- -# Argparse fix -#----------------------------------------------------------------------------- - -# Unfortunately argparse by default prints help messages to stderr instead of -# stdout. This makes it annoying to capture long help screens at the command -# line, since one must know how to pipe stderr, which many users don't know how -# to do. So we override the print_help method with one that defaults to -# stdout and use our class instead. - -class ArgumentParser(argparse.ArgumentParser): - """Simple argparse subclass that prints help to stdout by default.""" - - def print_help(self, file=None): - if file is None: - file = sys.stdout - return super(ArgumentParser, self).print_help(file) - - print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__ - -#----------------------------------------------------------------------------- -# Config class for holding config information -#----------------------------------------------------------------------------- - - -class Config(dict): - """An attribute based dict that can do smart merges.""" - - def __init__(self, *args, **kwds): - dict.__init__(self, *args, **kwds) - # This sets self.__dict__ = self, but it has to be done this way - # because we are also overriding __setattr__. - dict.__setattr__(self, '__dict__', self) - - def _merge(self, other): - to_update = {} - for k, v in other.iteritems(): - if k not in self: - to_update[k] = v - else: # I have this key - if isinstance(v, Config): - # Recursively merge common sub Configs - self[k]._merge(v) - else: - # Plain updates for non-Configs - to_update[k] = v - - self.update(to_update) - - def _is_section_key(self, key): - if key[0].upper()==key[0] and not key.startswith('_'): - return True - else: - return False - - def __contains__(self, key): - if self._is_section_key(key): - return True - else: - return super(Config, self).__contains__(key) - # .has_key is deprecated for dictionaries. - has_key = __contains__ - - def _has_section(self, key): - if self._is_section_key(key): - if super(Config, self).__contains__(key): - return True - return False - - def copy(self): - return type(self)(dict.copy(self)) - - def __copy__(self): - return self.copy() - - def __deepcopy__(self, memo): - import copy - return type(self)(copy.deepcopy(self.items())) - - def __getitem__(self, key): - # We cannot use directly self._is_section_key, because it triggers - # infinite recursion on top of PyPy. Instead, we manually fish the - # bound method. - is_section_key = self.__class__._is_section_key.__get__(self) - - # Because we use this for an exec namespace, we need to delegate - # the lookup of names in __builtin__ to itself. This means - # that you can't have section or attribute names that are - # builtins. - try: - return getattr(builtin_mod, key) - except AttributeError: - pass - if is_section_key(key): - try: - return dict.__getitem__(self, key) - except KeyError: - c = Config() - dict.__setitem__(self, key, c) - return c - else: - return dict.__getitem__(self, key) - - def __setitem__(self, key, value): - # Don't allow names in __builtin__ to be modified. - if hasattr(builtin_mod, key): - raise ConfigError('Config variable names cannot have the same name ' - 'as a Python builtin: %s' % key) - if self._is_section_key(key): - if not isinstance(value, Config): - raise ValueError('values whose keys begin with an uppercase ' - 'char must be Config instances: %r, %r' % (key, value)) - else: - dict.__setitem__(self, key, value) - - def __getattr__(self, key): - try: - return self.__getitem__(key) - except KeyError as e: - raise AttributeError(e) - - def __setattr__(self, key, value): - try: - self.__setitem__(key, value) - except KeyError as e: - raise AttributeError(e) - - def __delattr__(self, key): - try: - dict.__delitem__(self, key) - except KeyError as e: - raise AttributeError(e) - - -#----------------------------------------------------------------------------- -# Config loading classes -#----------------------------------------------------------------------------- - - -class ConfigLoader(object): - """A object for loading configurations from just about anywhere. - - The resulting configuration is packaged as a :class:`Struct`. - - Notes - ----- - A :class:`ConfigLoader` does one thing: load a config from a source - (file, command line arguments) and returns the data as a :class:`Struct`. - There are lots of things that :class:`ConfigLoader` does not do. It does - not implement complex logic for finding config files. It does not handle - default values or merge multiple configs. These things need to be - handled elsewhere. - """ - - def __init__(self): - """A base class for config loaders. - - Examples - -------- - - >>> cl = ConfigLoader() - >>> config = cl.load_config() - >>> config - {} - """ - self.clear() - - def clear(self): - self.config = Config() - - def load_config(self): - """Load a config from somewhere, return a :class:`Config` instance. - - Usually, this will cause self.config to be set and then returned. - However, in most cases, :meth:`ConfigLoader.clear` should be called - to erase any previous state. - """ - self.clear() - return self.config - - -class FileConfigLoader(ConfigLoader): - """A base class for file based configurations. - - As we add more file based config loaders, the common logic should go - here. - """ - pass - - -class PyFileConfigLoader(FileConfigLoader): - """A config loader for pure python files. - - This calls execfile on a plain python file and looks for attributes - that are all caps. These attribute are added to the config Struct. - """ - - def __init__(self, filename, path=None): - """Build a config loader for a filename and path. - - Parameters - ---------- - filename : str - The file name of the config file. - path : str, list, tuple - The path to search for the config file on, or a sequence of - paths to try in order. - """ - super(PyFileConfigLoader, self).__init__() - self.filename = filename - self.path = path - self.full_filename = '' - self.data = None - - def load_config(self): - """Load the config from a file and return it as a Struct.""" - self.clear() - try: - self._find_file() - except IOError as e: - raise ConfigFileNotFound(str(e)) - self._read_file_as_dict() - self._convert_to_config() - return self.config - - def _find_file(self): - """Try to find the file by searching the paths.""" - self.full_filename = filefind(self.filename, self.path) - - def _read_file_as_dict(self): - """Load the config file into self.config, with recursive loading.""" - # This closure is made available in the namespace that is used - # to exec the config file. It allows users to call - # load_subconfig('myconfig.py') to load config files recursively. - # It needs to be a closure because it has references to self.path - # and self.config. The sub-config is loaded with the same path - # as the parent, but it uses an empty config which is then merged - # with the parents. - - # If a profile is specified, the config file will be loaded - # from that profile - - def load_subconfig(fname, profile=None): - # import here to prevent circular imports - from IPython.core.profiledir import ProfileDir, ProfileDirError - if profile is not None: - try: - profile_dir = ProfileDir.find_profile_dir_by_name( - get_ipython_dir(), - profile, - ) - except ProfileDirError: - return - path = profile_dir.location - else: - path = self.path - loader = PyFileConfigLoader(fname, path) - try: - sub_config = loader.load_config() - except ConfigFileNotFound: - # Pass silently if the sub config is not there. This happens - # when a user s using a profile, but not the default config. - pass - else: - self.config._merge(sub_config) - - # Again, this needs to be a closure and should be used in config - # files to get the config being loaded. - def get_config(): - return self.config - - namespace = dict(load_subconfig=load_subconfig, get_config=get_config) - fs_encoding = sys.getfilesystemencoding() or 'ascii' - conf_filename = self.full_filename.encode(fs_encoding) - py3compat.execfile(conf_filename, namespace) - - def _convert_to_config(self): - if self.data is None: - ConfigLoaderError('self.data does not exist') - - -class CommandLineConfigLoader(ConfigLoader): - """A config loader for command line arguments. - - As we add more command line based loaders, the common logic should go - here. - """ - - def _exec_config_str(self, lhs, rhs): - """execute self.config. = - - * expands ~ with expanduser - * tries to assign with raw eval, otherwise assigns with just the string, - allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not* - equivalent are `--C.a=4` and `--C.a='4'`. - """ - rhs = os.path.expanduser(rhs) - try: - # Try to see if regular Python syntax will work. This - # won't handle strings as the quote marks are removed - # by the system shell. - value = eval(rhs) - except (NameError, SyntaxError): - # This case happens if the rhs is a string. - value = rhs - - exec u'self.config.%s = value' % lhs - - def _load_flag(self, cfg): - """update self.config from a flag, which can be a dict or Config""" - if isinstance(cfg, (dict, Config)): - # don't clobber whole config sections, update - # each section from config: - for sec,c in cfg.iteritems(): - self.config[sec].update(c) - else: - raise TypeError("Invalid flag: %r" % cfg) - -# raw --identifier=value pattern -# but *also* accept '-' as wordsep, for aliases -# accepts: --foo=a -# --Class.trait=value -# --alias-name=value -# rejects: -foo=value -# --foo -# --Class.trait -kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*') - -# just flags, no assignments, with two *or one* leading '-' -# accepts: --foo -# -foo-bar-again -# rejects: --anything=anything -# --two.word - -flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$') - -class KeyValueConfigLoader(CommandLineConfigLoader): - """A config loader that loads key value pairs from the command line. - - This allows command line options to be gives in the following form:: - - ipython --profile="foo" --InteractiveShell.autocall=False - """ - - def __init__(self, argv=None, aliases=None, flags=None): - """Create a key value pair config loader. - - Parameters - ---------- - argv : list - 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. - aliases : dict - A dict of aliases for configurable traits. - Keys are the short aliases, Values are the resolved trait. - Of the form: `{'alias' : 'Configurable.trait'}` - flags : dict - A dict of flags, keyed by str name. Vaues can be Config objects, - dicts, or "key=value" strings. If Config or dict, when the flag - is triggered, The flag is loaded as `self.config.update(m)`. - - Returns - ------- - config : Config - The resulting Config object. - - Examples - -------- - - >>> from IPython.config.loader import KeyValueConfigLoader - >>> cl = KeyValueConfigLoader() - >>> d = cl.load_config(["--A.name='brian'","--B.number=0"]) - >>> sorted(d.items()) - [('A', {'name': 'brian'}), ('B', {'number': 0})] - """ - self.clear() - if argv is None: - argv = sys.argv[1:] - self.argv = argv - self.aliases = aliases or {} - self.flags = flags or {} - - - def clear(self): - super(KeyValueConfigLoader, self).clear() - self.extra_args = [] - - - def _decode_argv(self, argv, enc=None): - """decode argv if bytes, using stin.encoding, falling back on default enc""" - uargv = [] - if enc is None: - enc = DEFAULT_ENCODING - for arg in argv: - if not isinstance(arg, unicode): - # only decode if not already decoded - arg = arg.decode(enc) - uargv.append(arg) - return uargv - - - def load_config(self, argv=None, aliases=None, flags=None): - """Parse the configuration and generate the Config object. - - After loading, any arguments that are not key-value or - flags will be stored in self.extra_args - a list of - unparsed command-line arguments. This is used for - arguments such as input files or subcommands. - - Parameters - ---------- - 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. - aliases : dict - A dict of aliases for configurable traits. - Keys are the short aliases, Values are the resolved trait. - Of the form: `{'alias' : 'Configurable.trait'}` - flags : dict - A dict of flags, keyed by str name. Values can be Config objects - or dicts. When the flag is triggered, The config is loaded as - `self.config.update(cfg)`. - """ - from IPython.config.configurable import Configurable - - self.clear() - if argv is None: - argv = self.argv - if aliases is None: - aliases = self.aliases - if flags is None: - flags = self.flags - - # ensure argv is a list of unicode strings: - uargv = self._decode_argv(argv) - for idx,raw in enumerate(uargv): - # strip leading '-' - item = raw.lstrip('-') - - if raw == '--': - # don't parse arguments after '--' - # this is useful for relaying arguments to scripts, e.g. - # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py - self.extra_args.extend(uargv[idx+1:]) - break - - if kv_pattern.match(raw): - lhs,rhs = item.split('=',1) - # Substitute longnames for aliases. - if lhs in aliases: - lhs = aliases[lhs] - if '.' not in lhs: - # probably a mistyped alias, but not technically illegal - warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs) - try: - self._exec_config_str(lhs, rhs) - except Exception: - raise ArgumentError("Invalid argument: '%s'" % raw) - - elif flag_pattern.match(raw): - if item in flags: - cfg,help = flags[item] - self._load_flag(cfg) - else: - raise ArgumentError("Unrecognized flag: '%s'"%raw) - elif raw.startswith('-'): - kv = '--'+item - if kv_pattern.match(kv): - raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv)) - else: - raise ArgumentError("Invalid argument: '%s'"%raw) - else: - # keep all args that aren't valid in a list, - # in case our parent knows what to do with them. - self.extra_args.append(item) - return self.config - -class ArgParseConfigLoader(CommandLineConfigLoader): - """A loader that uses the argparse module to load from the command line.""" - - def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw): - """Create a config loader for use with argparse. - - Parameters - ---------- - - argv : optional, list - If given, used to read command-line arguments from, otherwise - sys.argv[1:] is used. - - parser_args : tuple - A tuple of positional arguments that will be passed to the - constructor of :class:`argparse.ArgumentParser`. - - parser_kw : dict - A tuple of keyword arguments that will be passed to the - constructor of :class:`argparse.ArgumentParser`. - - Returns - ------- - config : Config - The resulting Config object. - """ - super(CommandLineConfigLoader, self).__init__() - self.clear() - if argv is None: - argv = sys.argv[1:] - self.argv = argv - self.aliases = aliases or {} - self.flags = flags or {} - - self.parser_args = parser_args - self.version = parser_kw.pop("version", None) - kwargs = dict(argument_default=argparse.SUPPRESS) - kwargs.update(parser_kw) - self.parser_kw = kwargs - - def load_config(self, argv=None, aliases=None, flags=None): - """Parse command line arguments and return as a Config object. - - Parameters - ---------- - - args : optional, list - If given, a list with the structure of sys.argv[1:] to parse - arguments from. If not given, the instance's self.argv attribute - (given at construction time) is used.""" - self.clear() - if argv is None: - argv = self.argv - if aliases is None: - aliases = self.aliases - if flags is None: - flags = self.flags - self._create_parser(aliases, flags) - self._parse_args(argv) - self._convert_to_config() - return self.config - - def get_extra_args(self): - if hasattr(self, 'extra_args'): - return self.extra_args - else: - return [] - - def _create_parser(self, aliases=None, flags=None): - self.parser = ArgumentParser(*self.parser_args, **self.parser_kw) - self._add_arguments(aliases, flags) - - def _add_arguments(self, aliases=None, flags=None): - raise NotImplementedError("subclasses must implement _add_arguments") - - def _parse_args(self, args): - """self.parser->self.parsed_data""" - # decode sys.argv to support unicode command-line options - enc = DEFAULT_ENCODING - uargs = [py3compat.cast_unicode(a, enc) for a in args] - self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs) - - def _convert_to_config(self): - """self.parsed_data->self.config""" - for k, v in vars(self.parsed_data).iteritems(): - exec "self.config.%s = v"%k in locals(), globals() - -class KVArgParseConfigLoader(ArgParseConfigLoader): - """A config loader that loads aliases and flags with argparse, - but will use KVLoader for the rest. This allows better parsing - of common args, such as `ipython -c 'print 5'`, but still gets - arbitrary config with `ipython --InteractiveShell.use_readline=False`""" - - def _add_arguments(self, aliases=None, flags=None): - self.alias_flags = {} - # print aliases, flags - if aliases is None: - aliases = self.aliases - if flags is None: - flags = self.flags - paa = self.parser.add_argument - for key,value in aliases.iteritems(): - if key in flags: - # flags - nargs = '?' - else: - nargs = None - if len(key) is 1: - paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs) - else: - paa('--'+key, type=unicode, dest=value, nargs=nargs) - for key, (value, help) in flags.iteritems(): - if key in self.aliases: - # - self.alias_flags[self.aliases[key]] = value - continue - if len(key) is 1: - paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value) - else: - paa('--'+key, action='append_const', dest='_flags', const=value) - - def _convert_to_config(self): - """self.parsed_data->self.config, parse unrecognized extra args via KVLoader.""" - # remove subconfigs list from namespace before transforming the Namespace - if '_flags' in self.parsed_data: - subcs = self.parsed_data._flags - del self.parsed_data._flags - else: - subcs = [] - - for k, v in vars(self.parsed_data).iteritems(): - if v is None: - # it was a flag that shares the name of an alias - subcs.append(self.alias_flags[k]) - else: - # eval the KV assignment - self._exec_config_str(k, v) - - for subc in subcs: - self._load_flag(subc) - - if self.extra_args: - sub_parser = KeyValueConfigLoader() - sub_parser.load_config(self.extra_args) - self.config._merge(sub_parser.config) - self.extra_args = sub_parser.extra_args - - -def load_pyconfig_files(config_files, path): - """Load multiple Python config files, merging each of them in turn. - - Parameters - ========== - config_files : list of str - List of config files names to load and merge into the config. - path : unicode - The full path to the location of the config files. - """ - config = Config() - for cf in config_files: - loader = PyFileConfigLoader(cf, path=path) - try: - next_config = loader.load_config() - except ConfigFileNotFound: - pass - except: - raise - else: - config._merge(next_config) - return config diff --git a/IPython/utils/text.py b/IPython/utils/text.py deleted file mode 100644 index 86d52a1..0000000 --- a/IPython/utils/text.py +++ /dev/null @@ -1,850 +0,0 @@ -# encoding: utf-8 -""" -Utilities for working with strings and text. - -Inheritance diagram: - -.. inheritance-diagram:: IPython.utils.text - :parts: 3 -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import __main__ - -import os -import re -import shutil -import sys -import textwrap -from string import Formatter - -from IPython.external.path import path -from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest -from IPython.utils import py3compat -from IPython.utils.io import nlprint -from IPython.utils.data import flatten - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - -def unquote_ends(istr): - """Remove a single pair of quotes from the endpoints of a string.""" - - if not istr: - return istr - if (istr[0]=="'" and istr[-1]=="'") or \ - (istr[0]=='"' and istr[-1]=='"'): - return istr[1:-1] - else: - return istr - - -class LSString(str): - """String derivative with a special access attributes. - - These are normal strings, but with the special attributes: - - .l (or .list) : value as list (split on newlines). - .n (or .nlstr): original value (the string itself). - .s (or .spstr): value as whitespace-separated string. - .p (or .paths): list of path objects - - Any values which require transformations are computed only once and - cached. - - Such strings are very useful to efficiently interact with the shell, which - typically only understands whitespace-separated options for commands.""" - - def get_list(self): - try: - return self.__list - except AttributeError: - self.__list = self.split('\n') - return self.__list - - l = list = property(get_list) - - def get_spstr(self): - try: - return self.__spstr - except AttributeError: - self.__spstr = self.replace('\n',' ') - return self.__spstr - - s = spstr = property(get_spstr) - - def get_nlstr(self): - return self - - n = nlstr = property(get_nlstr) - - def get_paths(self): - try: - return self.__paths - except AttributeError: - self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)] - return self.__paths - - p = paths = property(get_paths) - -# FIXME: We need to reimplement type specific displayhook and then add this -# back as a custom printer. This should also be moved outside utils into the -# core. - -# def print_lsstring(arg): -# """ Prettier (non-repr-like) and more informative printer for LSString """ -# print "LSString (.p, .n, .l, .s available). Value:" -# print arg -# -# -# print_lsstring = result_display.when_type(LSString)(print_lsstring) - - -class SList(list): - """List derivative with a special access attributes. - - These are normal lists, but with the special attributes: - - .l (or .list) : value as list (the list itself). - .n (or .nlstr): value as a string, joined on newlines. - .s (or .spstr): value as a string, joined on spaces. - .p (or .paths): list of path objects - - Any values which require transformations are computed only once and - cached.""" - - def get_list(self): - return self - - l = list = property(get_list) - - def get_spstr(self): - try: - return self.__spstr - except AttributeError: - self.__spstr = ' '.join(self) - return self.__spstr - - s = spstr = property(get_spstr) - - def get_nlstr(self): - try: - return self.__nlstr - except AttributeError: - self.__nlstr = '\n'.join(self) - return self.__nlstr - - n = nlstr = property(get_nlstr) - - def get_paths(self): - try: - return self.__paths - except AttributeError: - self.__paths = [path(p) for p in self if os.path.exists(p)] - return self.__paths - - p = paths = property(get_paths) - - def grep(self, pattern, prune = False, field = None): - """ Return all strings matching 'pattern' (a regex or callable) - - This is case-insensitive. If prune is true, return all items - NOT matching the pattern. - - If field is specified, the match must occur in the specified - whitespace-separated field. - - Examples:: - - a.grep( lambda x: x.startswith('C') ) - a.grep('Cha.*log', prune=1) - a.grep('chm', field=-1) - """ - - def match_target(s): - if field is None: - return s - parts = s.split() - try: - tgt = parts[field] - return tgt - except IndexError: - return "" - - if isinstance(pattern, basestring): - pred = lambda x : re.search(pattern, x, re.IGNORECASE) - else: - pred = pattern - if not prune: - return SList([el for el in self if pred(match_target(el))]) - else: - return SList([el for el in self if not pred(match_target(el))]) - - def fields(self, *fields): - """ Collect whitespace-separated fields from string list - - Allows quick awk-like usage of string lists. - - Example data (in var a, created by 'a = !ls -l'):: - -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog - drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython - - a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+'] - a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+'] - (note the joining by space). - a.fields(-1) is ['ChangeLog', 'IPython'] - - IndexErrors are ignored. - - Without args, fields() just split()'s the strings. - """ - if len(fields) == 0: - return [el.split() for el in self] - - res = SList() - for el in [f.split() for f in self]: - lineparts = [] - - for fd in fields: - try: - lineparts.append(el[fd]) - except IndexError: - pass - if lineparts: - res.append(" ".join(lineparts)) - - return res - - def sort(self,field= None, nums = False): - """ sort by specified fields (see fields()) - - Example:: - a.sort(1, nums = True) - - Sorts a by second field, in numerical order (so that 21 > 3) - - """ - - #decorate, sort, undecorate - if field is not None: - dsu = [[SList([line]).fields(field), line] for line in self] - else: - dsu = [[line, line] for line in self] - if nums: - for i in range(len(dsu)): - numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()]) - try: - n = int(numstr) - except ValueError: - n = 0; - dsu[i][0] = n - - - dsu.sort() - return SList([t[1] for t in dsu]) - - -# FIXME: We need to reimplement type specific displayhook and then add this -# back as a custom printer. This should also be moved outside utils into the -# core. - -# def print_slist(arg): -# """ Prettier (non-repr-like) and more informative printer for SList """ -# print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):" -# if hasattr(arg, 'hideonce') and arg.hideonce: -# arg.hideonce = False -# return -# -# nlprint(arg) -# -# print_slist = result_display.when_type(SList)(print_slist) - - -def esc_quotes(strng): - """Return the input string with single and double quotes escaped out""" - - return strng.replace('"','\\"').replace("'","\\'") - - -def qw(words,flat=0,sep=None,maxsplit=-1): - """Similar to Perl's qw() operator, but with some more options. - - qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit) - - words can also be a list itself, and with flat=1, the output will be - recursively flattened. - - Examples: - - >>> qw('1 2') - ['1', '2'] - - >>> qw(['a b','1 2',['m n','p q']]) - [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]] - - >>> qw(['a b','1 2',['m n','p q']],flat=1) - ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] - """ - - if isinstance(words, basestring): - return [word.strip() for word in words.split(sep,maxsplit) - if word and not word.isspace() ] - if flat: - return flatten(map(qw,words,[1]*len(words))) - return map(qw,words) - - -def qwflat(words,sep=None,maxsplit=-1): - """Calls qw(words) in flat mode. It's just a convenient shorthand.""" - return qw(words,1,sep,maxsplit) - - -def qw_lol(indata): - """qw_lol('a b') -> [['a','b']], - otherwise it's just a call to qw(). - - We need this to make sure the modules_some keys *always* end up as a - list of lists.""" - - if isinstance(indata, basestring): - return [qw(indata)] - else: - return qw(indata) - - -def grep(pat,list,case=1): - """Simple minded grep-like function. - grep(pat,list) returns occurrences of pat in list, None on failure. - - It only does simple string matching, with no support for regexps. Use the - option case=0 for case-insensitive matching.""" - - # This is pretty crude. At least it should implement copying only references - # to the original data in case it's big. Now it copies the data for output. - out=[] - if case: - for term in list: - if term.find(pat)>-1: out.append(term) - else: - lpat=pat.lower() - for term in list: - if term.lower().find(lpat)>-1: out.append(term) - - if len(out): return out - else: return None - - -def dgrep(pat,*opts): - """Return grep() on dir()+dir(__builtins__). - - A very common use of grep() when working interactively.""" - - return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts) - - -def idgrep(pat): - """Case-insensitive dgrep()""" - - return dgrep(pat,0) - - -def igrep(pat,list): - """Synonym for case-insensitive grep.""" - - return grep(pat,list,case=0) - - -def indent(instr,nspaces=4, ntabs=0, flatten=False): - """Indent a string a given number of spaces or tabstops. - - indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. - - Parameters - ---------- - - instr : basestring - The string to be indented. - nspaces : int (default: 4) - The number of spaces to be indented. - ntabs : int (default: 0) - The number of tabs to be indented. - flatten : bool (default: False) - Whether to scrub existing indentation. If True, all lines will be - aligned to the same indentation. If False, existing indentation will - be strictly increased. - - Returns - ------- - - str|unicode : string indented by ntabs and nspaces. - - """ - if instr is None: - return - ind = '\t'*ntabs+' '*nspaces - if flatten: - pat = re.compile(r'^\s*', re.MULTILINE) - else: - pat = re.compile(r'^', re.MULTILINE) - outstr = re.sub(pat, ind, instr) - if outstr.endswith(os.linesep+ind): - return outstr[:-len(ind)] - else: - return outstr - -def native_line_ends(filename,backup=1): - """Convert (in-place) a file to line-ends native to the current OS. - - If the optional backup argument is given as false, no backup of the - original file is left. """ - - backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'} - - bak_filename = filename + backup_suffixes[os.name] - - original = open(filename).read() - shutil.copy2(filename,bak_filename) - try: - new = open(filename,'wb') - new.write(os.linesep.join(original.splitlines())) - new.write(os.linesep) # ALWAYS put an eol at the end of the file - new.close() - except: - os.rename(bak_filename,filename) - if not backup: - try: - os.remove(bak_filename) - except: - pass - - -def list_strings(arg): - """Always return a list of strings, given a string or list of strings - as input. - - :Examples: - - In [7]: list_strings('A single string') - Out[7]: ['A single string'] - - In [8]: list_strings(['A single string in a list']) - Out[8]: ['A single string in a list'] - - In [9]: list_strings(['A','list','of','strings']) - Out[9]: ['A', 'list', 'of', 'strings'] - """ - - if isinstance(arg,basestring): return [arg] - else: return arg - - -def marquee(txt='',width=78,mark='*'): - """Return the input string centered in a 'marquee'. - - :Examples: - - In [16]: marquee('A test',40) - Out[16]: '**************** A test ****************' - - In [17]: marquee('A test',40,'-') - Out[17]: '---------------- A test ----------------' - - In [18]: marquee('A test',40,' ') - Out[18]: ' A test ' - - """ - if not txt: - return (mark*width)[:width] - nmark = (width-len(txt)-2)//len(mark)//2 - if nmark < 0: nmark =0 - marks = mark*nmark - return '%s %s %s' % (marks,txt,marks) - - -ini_spaces_re = re.compile(r'^(\s+)') - -def num_ini_spaces(strng): - """Return the number of initial spaces in a string""" - - ini_spaces = ini_spaces_re.match(strng) - if ini_spaces: - return ini_spaces.end() - else: - return 0 - - -def format_screen(strng): - """Format a string for screen printing. - - This removes some latex-type format codes.""" - # Paragraph continue - par_re = re.compile(r'\\$',re.MULTILINE) - 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 - - -def long_substr(data): - """Return the longest common substring in a list of strings. - - Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python - """ - substr = '' - if len(data) > 1 and len(data[0]) > 0: - for i in range(len(data[0])): - for j in range(len(data[0])-i+1): - if j > len(substr) and all(data[0][i:i+j] in x for x in data): - substr = data[0][i:i+j] - elif len(data) == 1: - substr = data[0] - return substr - - -def strip_email_quotes(text): - """Strip leading email quotation characters ('>'). - - Removes any combination of leading '>' interspersed with whitespace that - appears *identically* in all lines of the input text. - - Parameters - ---------- - text : str - - Examples - -------- - - Simple uses:: - - In [2]: strip_email_quotes('> > text') - Out[2]: 'text' - - In [3]: strip_email_quotes('> > text\\n> > more') - Out[3]: 'text\\nmore' - - Note how only the common prefix that appears in all lines is stripped:: - - In [4]: strip_email_quotes('> > text\\n> > more\\n> more...') - Out[4]: '> text\\n> more\\nmore...' - - So if any line has no quote marks ('>') , then none are stripped from any - of them :: - - In [5]: strip_email_quotes('> > text\\n> > more\\nlast different') - Out[5]: '> > text\\n> > more\\nlast different' - """ - lines = text.splitlines() - matches = set() - for line in lines: - prefix = re.match(r'^(\s*>[ >]*)', line) - if prefix: - matches.add(prefix.group(1)) - else: - break - else: - prefix = long_substr(list(matches)) - if prefix: - strip = len(prefix) - text = '\n'.join([ ln[strip:] for ln in lines]) - return text - - -class EvalFormatter(Formatter): - """A String Formatter that allows evaluation of simple expressions. - - Note that this version interprets a : as specifying a format string (as per - standard string formatting), so if slicing is required, you must explicitly - create a slice. - - This is to be used in templating cases, such as the parallel batch - script templates, where simple arithmetic on arguments is useful. - - Examples - -------- - - In [1]: f = EvalFormatter() - In [2]: f.format('{n//4}', n=8) - Out [2]: '2' - - In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello") - Out [3]: 'll' - """ - def get_field(self, name, args, kwargs): - v = eval(name, kwargs) - return v, name - - -@skip_doctest_py3 -class FullEvalFormatter(Formatter): - """A String Formatter that allows evaluation of simple expressions. - - Any time a format key is not found in the kwargs, - it will be tried as an expression in the kwargs namespace. - - Note that this version allows slicing using [1:2], so you cannot specify - a format string. Use :class:`EvalFormatter` to permit format strings. - - Examples - -------- - - In [1]: f = FullEvalFormatter() - In [2]: f.format('{n//4}', n=8) - Out[2]: u'2' - - In [3]: f.format('{list(range(5))[2:4]}') - Out[3]: u'[2, 3]' - - In [4]: f.format('{3*2}') - Out[4]: u'6' - """ - # copied from Formatter._vformat with minor changes to allow eval - # and replace the format_spec code with slicing - def _vformat(self, format_string, args, kwargs, used_args, recursion_depth): - if recursion_depth < 0: - raise ValueError('Max string recursion exceeded') - result = [] - for literal_text, field_name, format_spec, conversion in \ - self.parse(format_string): - - # output the literal text - if literal_text: - result.append(literal_text) - - # if there's a field, output it - if field_name is not None: - # this is some markup, find the object and do - # the formatting - - if format_spec: - # override format spec, to allow slicing: - field_name = ':'.join([field_name, format_spec]) - - # eval the contents of the field for the object - # to be formatted - obj = eval(field_name, kwargs) - - # do any conversion on the resulting object - obj = self.convert_field(obj, conversion) - - # format the object and append to the result - result.append(self.format_field(obj, '')) - - return u''.join(py3compat.cast_unicode(s) for s in result) - - -@skip_doctest_py3 -class DollarFormatter(FullEvalFormatter): - """Formatter allowing Itpl style $foo replacement, for names and attribute - access only. Standard {foo} replacement also works, and allows full - evaluation of its arguments. - - Examples - -------- - In [1]: f = DollarFormatter() - In [2]: f.format('{n//4}', n=8) - Out[2]: u'2' - - In [3]: f.format('23 * 76 is $result', result=23*76) - Out[3]: u'23 * 76 is 1748' - - In [4]: f.format('$a or {b}', a=1, b=2) - Out[4]: u'1 or 2' - """ - _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)") - def parse(self, fmt_string): - for literal_txt, field_name, format_spec, conversion \ - in Formatter.parse(self, fmt_string): - - # Find $foo patterns in the literal text. - continue_from = 0 - txt = "" - for m in self._dollar_pattern.finditer(literal_txt): - new_txt, new_field = m.group(1,2) - # $$foo --> $foo - if new_field.startswith("$"): - txt += new_txt + new_field - else: - yield (txt + new_txt, new_field, "", None) - txt = "" - continue_from = m.end() - - # Re-yield the {foo} style pattern - yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion) - -#----------------------------------------------------------------------------- -# Utils to columnize a list of string -#----------------------------------------------------------------------------- - -def _chunks(l, n): - """Yield successive n-sized chunks from l.""" - for i in xrange(0, len(l), n): - yield l[i:i+n] - - -def _find_optimal(rlist , separator_size=2 , displaywidth=80): - """Calculate optimal info to columnize a list of string""" - for nrow in range(1, len(rlist)+1) : - chk = map(max,_chunks(rlist, nrow)) - sumlength = sum(chk) - ncols = len(chk) - if sumlength+separator_size*(ncols-1) <= displaywidth : - break; - return {'columns_numbers' : ncols, - 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0, - 'rows_numbers' : nrow, - 'columns_width' : chk - } - - -def _get_or_default(mylist, i, default=None): - """return list item number, or default if don't exist""" - if i >= len(mylist): - return default - else : - return mylist[i] - - -@skip_doctest -def compute_item_matrix(items, empty=None, *args, **kwargs) : - """Returns a nested list, and info to columnize items - - Parameters : - ------------ - - items : - list of strings to columize - empty : (default None) - default value to fill list if needed - separator_size : int (default=2) - How much caracters will be used as a separation between each columns. - displaywidth : int (default=80) - The width of the area onto wich the columns should enter - - Returns : - --------- - - Returns a tuple of (strings_matrix, dict_info) - - strings_matrix : - - nested list of string, the outer most list contains as many list as - rows, the innermost lists have each as many element as colums. If the - total number of elements in `items` does not equal the product of - rows*columns, the last element of some lists are filled with `None`. - - dict_info : - some info to make columnize easier: - - columns_numbers : number of columns - rows_numbers : number of rows - columns_width : list of with of each columns - optimal_separator_width : best separator width between columns - - Exemple : - --------- - - In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l'] - ...: compute_item_matrix(l,displaywidth=12) - Out[1]: - ([['aaa', 'f', 'k'], - ['b', 'g', 'l'], - ['cc', 'h', None], - ['d', 'i', None], - ['eeeee', 'j', None]], - {'columns_numbers': 3, - 'columns_width': [5, 1, 1], - 'optimal_separator_width': 2, - 'rows_numbers': 5}) - - """ - info = _find_optimal(map(len, items), *args, **kwargs) - nrow, ncol = info['rows_numbers'], info['columns_numbers'] - return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info) - - -def columnize(items, separator=' ', displaywidth=80): - """ Transform a list of strings into a single string with columns. - - Parameters - ---------- - items : sequence of strings - The strings to process. - - separator : str, optional [default is two spaces] - The string that separates columns. - - displaywidth : int, optional [default is 80] - Width of the display in number of characters. - - Returns - ------- - The formatted string. - """ - if not items : - return '\n' - matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth) - fmatrix = [filter(None, x) for x in matrix] - sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])]) - return '\n'.join(map(sjoin, fmatrix))+'\n' diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py deleted file mode 100644 index 21764e7..0000000 --- a/IPython/utils/traitlets.py +++ /dev/null @@ -1,1439 +0,0 @@ -# encoding: utf-8 -""" -A lightweight Traits like module. - -This is designed to provide a lightweight, simple, pure Python version of -many of the capabilities of enthought.traits. This includes: - -* Validation -* Type specification with defaults -* Static and dynamic notification -* Basic predefined types -* An API that is similar to enthought.traits - -We don't support: - -* Delegation -* Automatic GUI generation -* A full set of trait types. Most importantly, we don't provide container - traits (list, dict, tuple) that can trigger notifications if their - contents change. -* API compatibility with enthought.traits - -There are also some important difference in our design: - -* enthought.traits does not validate default values. We do. - -We choose to create this module because we need these capabilities, but -we need them to be pure Python so they work in all Python implementations, -including Jython and IronPython. - -Inheritance diagram: - -.. inheritance-diagram:: IPython.utils.traitlets - :parts: 3 - -Authors: - -* Brian Granger -* Enthought, Inc. Some of the code in this file comes from enthought.traits - and is licensed under the BSD license. Also, many of the ideas also come - from enthought.traits even though our implementation is very different. -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - - -import inspect -import re -import sys -import types -from types import FunctionType -try: - from types import ClassType, InstanceType - ClassTypes = (ClassType, type) -except: - ClassTypes = (type,) - -from .importstring import import_item -from IPython.utils import py3compat - -SequenceTypes = (list, tuple, set, frozenset) - -#----------------------------------------------------------------------------- -# Basic classes -#----------------------------------------------------------------------------- - - -class NoDefaultSpecified ( object ): pass -NoDefaultSpecified = NoDefaultSpecified() - - -class Undefined ( object ): pass -Undefined = Undefined() - -class TraitError(Exception): - pass - -#----------------------------------------------------------------------------- -# Utilities -#----------------------------------------------------------------------------- - - -def class_of ( object ): - """ Returns a string containing the class name of an object with the - correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image', - 'a PlotValue'). - """ - if isinstance( object, basestring ): - return add_article( object ) - - return add_article( object.__class__.__name__ ) - - -def add_article ( name ): - """ Returns a string containing the correct indefinite article ('a' or 'an') - prefixed to the specified string. - """ - if name[:1].lower() in 'aeiou': - return 'an ' + name - - return 'a ' + name - - -def repr_type(obj): - """ Return a string representation of a value and its type for readable - error messages. - """ - the_type = type(obj) - if (not py3compat.PY3) and the_type is InstanceType: - # Old-style class. - the_type = obj.__class__ - msg = '%r %r' % (obj, the_type) - return msg - - -def is_trait(t): - """ Returns whether the given value is an instance or subclass of TraitType. - """ - return (isinstance(t, TraitType) or - (isinstance(t, type) and issubclass(t, TraitType))) - - -def parse_notifier_name(name): - """Convert the name argument to a list of names. - - Examples - -------- - - >>> parse_notifier_name('a') - ['a'] - >>> parse_notifier_name(['a','b']) - ['a', 'b'] - >>> parse_notifier_name(None) - ['anytrait'] - """ - if isinstance(name, str): - return [name] - elif name is None: - return ['anytrait'] - elif isinstance(name, (list, tuple)): - for n in name: - assert isinstance(n, str), "names must be strings" - return name - - -class _SimpleTest: - def __init__ ( self, value ): self.value = value - def __call__ ( self, test ): - return test == self.value - def __repr__(self): - return " 0: - if len(self.metadata) > 0: - self._metadata = self.metadata.copy() - self._metadata.update(metadata) - else: - self._metadata = metadata - else: - self._metadata = self.metadata - - self.init() - - def init(self): - pass - - def get_default_value(self): - """Create a new instance of the default value.""" - return self.default_value - - def instance_init(self, obj): - """This is called by :meth:`HasTraits.__new__` to finish init'ing. - - Some stages of initialization must be delayed until the parent - :class:`HasTraits` instance has been created. This method is - called in :meth:`HasTraits.__new__` after the instance has been - created. - - This method trigger the creation and validation of default values - and also things like the resolution of str given class names in - :class:`Type` and :class`Instance`. - - Parameters - ---------- - obj : :class:`HasTraits` instance - The parent :class:`HasTraits` instance that has just been - created. - """ - self.set_default_value(obj) - - def set_default_value(self, obj): - """Set the default value on a per instance basis. - - This method is called by :meth:`instance_init` to create and - validate the default value. The creation and validation of - default values must be delayed until the parent :class:`HasTraits` - class has been instantiated. - """ - # Check for a deferred initializer defined in the same class as the - # trait declaration or above. - mro = type(obj).mro() - meth_name = '_%s_default' % self.name - for cls in mro[:mro.index(self.this_class)+1]: - if meth_name in cls.__dict__: - break - else: - # We didn't find one. Do static initialization. - dv = self.get_default_value() - newdv = self._validate(obj, dv) - obj._trait_values[self.name] = newdv - return - # Complete the dynamic initialization. - obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name] - - def __get__(self, obj, cls=None): - """Get the value of the trait by self.name for the instance. - - Default values are instantiated when :meth:`HasTraits.__new__` - is called. Thus by the time this method gets called either the - default value or a user defined value (they called :meth:`__set__`) - is in the :class:`HasTraits` instance. - """ - if obj is None: - return self - else: - try: - value = obj._trait_values[self.name] - except KeyError: - # Check for a dynamic initializer. - if self.name in obj._trait_dyn_inits: - value = obj._trait_dyn_inits[self.name](obj) - # FIXME: Do we really validate here? - value = self._validate(obj, value) - obj._trait_values[self.name] = value - return value - else: - raise TraitError('Unexpected error in TraitType: ' - 'both default value and dynamic initializer are ' - 'absent.') - except Exception: - # HasTraits should call set_default_value to populate - # this. So this should never be reached. - raise TraitError('Unexpected error in TraitType: ' - 'default value not set properly') - else: - return value - - def __set__(self, obj, value): - new_value = self._validate(obj, value) - old_value = self.__get__(obj) - obj._trait_values[self.name] = new_value - if old_value != new_value: - obj._notify_trait(self.name, old_value, new_value) - - def _validate(self, obj, value): - if hasattr(self, 'validate'): - return self.validate(obj, value) - elif hasattr(self, 'is_valid_for'): - valid = self.is_valid_for(value) - if valid: - return value - else: - raise TraitError('invalid value for type: %r' % value) - elif hasattr(self, 'value_for'): - return self.value_for(value) - else: - return value - - def info(self): - return self.info_text - - def error(self, obj, value): - if obj is not None: - e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ - % (self.name, class_of(obj), - self.info(), repr_type(value)) - else: - e = "The '%s' trait must be %s, but a value of %r was specified." \ - % (self.name, self.info(), repr_type(value)) - raise TraitError(e) - - def get_metadata(self, key): - return getattr(self, '_metadata', {}).get(key, None) - - def set_metadata(self, key, value): - getattr(self, '_metadata', {})[key] = value - - -#----------------------------------------------------------------------------- -# The HasTraits implementation -#----------------------------------------------------------------------------- - - -class MetaHasTraits(type): - """A metaclass for HasTraits. - - This metaclass makes sure that any TraitType class attributes are - instantiated and sets their name attribute. - """ - - def __new__(mcls, name, bases, classdict): - """Create the HasTraits class. - - This instantiates all TraitTypes in the class dict and sets their - :attr:`name` attribute. - """ - # print "MetaHasTraitlets (mcls, name): ", mcls, name - # print "MetaHasTraitlets (bases): ", bases - # print "MetaHasTraitlets (classdict): ", classdict - for k,v in classdict.iteritems(): - if isinstance(v, TraitType): - v.name = k - elif inspect.isclass(v): - if issubclass(v, TraitType): - vinst = v() - vinst.name = k - classdict[k] = vinst - return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict) - - def __init__(cls, name, bases, classdict): - """Finish initializing the HasTraits class. - - This sets the :attr:`this_class` attribute of each TraitType in the - class dict to the newly created class ``cls``. - """ - for k, v in classdict.iteritems(): - if isinstance(v, TraitType): - v.this_class = cls - super(MetaHasTraits, cls).__init__(name, bases, classdict) - -class HasTraits(object): - - __metaclass__ = MetaHasTraits - - def __new__(cls, **kw): - # This is needed because in Python 2.6 object.__new__ only accepts - # the cls argument. - new_meth = super(HasTraits, cls).__new__ - if new_meth is object.__new__: - inst = new_meth(cls) - else: - inst = new_meth(cls, **kw) - inst._trait_values = {} - inst._trait_notifiers = {} - inst._trait_dyn_inits = {} - # Here we tell all the TraitType instances to set their default - # values on the instance. - for key in dir(cls): - # Some descriptors raise AttributeError like zope.interface's - # __provides__ attributes even though they exist. This causes - # AttributeErrors even though they are listed in dir(cls). - try: - value = getattr(cls, key) - except AttributeError: - pass - else: - if isinstance(value, TraitType): - value.instance_init(inst) - - return inst - - def __init__(self, **kw): - # Allow trait values to be set using keyword arguments. - # We need to use setattr for this to trigger validation and - # notifications. - for key, value in kw.iteritems(): - setattr(self, key, value) - - def _notify_trait(self, name, old_value, new_value): - - # First dynamic ones - callables = self._trait_notifiers.get(name,[]) - more_callables = self._trait_notifiers.get('anytrait',[]) - callables.extend(more_callables) - - # Now static ones - try: - cb = getattr(self, '_%s_changed' % name) - except: - pass - else: - callables.append(cb) - - # Call them all now - for c in callables: - # Traits catches and logs errors here. I allow them to raise - if callable(c): - argspec = inspect.getargspec(c) - nargs = len(argspec[0]) - # Bound methods have an additional 'self' argument - # I don't know how to treat unbound methods, but they - # can't really be used for callbacks. - if isinstance(c, types.MethodType): - offset = -1 - else: - offset = 0 - if nargs + offset == 0: - c() - elif nargs + offset == 1: - c(name) - elif nargs + offset == 2: - c(name, new_value) - elif nargs + offset == 3: - c(name, old_value, new_value) - else: - raise TraitError('a trait changed callback ' - 'must have 0-3 arguments.') - else: - raise TraitError('a trait changed callback ' - 'must be callable.') - - - def _add_notifiers(self, handler, name): - if name not in self._trait_notifiers: - nlist = [] - self._trait_notifiers[name] = nlist - else: - nlist = self._trait_notifiers[name] - if handler not in nlist: - nlist.append(handler) - - def _remove_notifiers(self, handler, name): - if name in self._trait_notifiers: - nlist = self._trait_notifiers[name] - try: - index = nlist.index(handler) - except ValueError: - pass - else: - del nlist[index] - - def on_trait_change(self, handler, name=None, remove=False): - """Setup a handler to be called when a trait changes. - - This is used to setup dynamic notifications of trait changes. - - Static handlers can be created by creating methods on a HasTraits - subclass with the naming convention '_[traitname]_changed'. Thus, - to create static handler for the trait 'a', create the method - _a_changed(self, name, old, new) (fewer arguments can be used, see - below). - - Parameters - ---------- - handler : callable - A callable that is called when a trait changes. Its - signature can be handler(), handler(name), handler(name, new) - or handler(name, old, new). - name : list, str, None - If None, the handler will apply to all traits. If a list - of str, handler will apply to all names in the list. If a - str, the handler will apply just to that name. - remove : bool - If False (the default), then install the handler. If True - then unintall it. - """ - if remove: - names = parse_notifier_name(name) - for n in names: - self._remove_notifiers(handler, n) - else: - names = parse_notifier_name(name) - 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() - - def traits(self, **metadata): - """Get a list of all the traits of this class. - - 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(self.__class__) 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_metadata(self, traitname, key): - """Get metadata values for trait by key.""" - try: - trait = getattr(self.__class__, traitname) - except AttributeError: - raise TraitError("Class %s does not have a trait named %s" % - (self.__class__.__name__, traitname)) - else: - return trait.get_metadata(key) - -#----------------------------------------------------------------------------- -# Actual TraitTypes implementations/subclasses -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# TraitTypes subclasses for handling classes and instances of classes -#----------------------------------------------------------------------------- - - -class ClassBasedTraitType(TraitType): - """A trait with error reporting for Type, Instance and This.""" - - def error(self, obj, value): - kind = type(value) - if (not py3compat.PY3) and kind is InstanceType: - msg = 'class %s' % value.__class__.__name__ - else: - msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) - - if obj is not None: - e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ - % (self.name, class_of(obj), - self.info(), msg) - else: - e = "The '%s' trait must be %s, but a value of %r was specified." \ - % (self.name, self.info(), msg) - - raise TraitError(e) - - -class Type(ClassBasedTraitType): - """A trait whose value must be a subclass of a specified class.""" - - def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ): - """Construct a Type trait - - A Type trait specifies that its values must be subclasses of - a particular class. - - If only ``default_value`` is given, it is used for the ``klass`` as - well. - - Parameters - ---------- - default_value : class, str or None - The default value must be a subclass of klass. If an str, - the str must be a fully specified class name, like 'foo.bar.Bah'. - The string is resolved into real class, when the parent - :class:`HasTraits` class is instantiated. - klass : class, str, None - Values of this trait must be a subclass of klass. The klass - may be specified in a string like: 'foo.bar.MyClass'. - The string is resolved into real class, when the parent - :class:`HasTraits` class is instantiated. - allow_none : boolean - Indicates whether None is allowed as an assignable value. Even if - ``False``, the default value may be ``None``. - """ - if default_value is None: - if klass is None: - klass = object - elif klass is None: - klass = default_value - - if not (inspect.isclass(klass) or isinstance(klass, basestring)): - raise TraitError("A Type trait must specify a class.") - - self.klass = klass - self._allow_none = allow_none - - super(Type, self).__init__(default_value, **metadata) - - def validate(self, obj, value): - """Validates that the value is a valid object instance.""" - try: - if issubclass(value, self.klass): - return value - except: - if (value is None) and (self._allow_none): - return value - - self.error(obj, value) - - def info(self): - """ Returns a description of the trait.""" - if isinstance(self.klass, basestring): - klass = self.klass - else: - klass = self.klass.__name__ - result = 'a subclass of ' + klass - if self._allow_none: - return result + ' or None' - return result - - def instance_init(self, obj): - self._resolve_classes() - super(Type, self).instance_init(obj) - - def _resolve_classes(self): - if isinstance(self.klass, basestring): - self.klass = import_item(self.klass) - if isinstance(self.default_value, basestring): - self.default_value = import_item(self.default_value) - - def get_default_value(self): - return self.default_value - - -class DefaultValueGenerator(object): - """A class for generating new default value instances.""" - - def __init__(self, *args, **kw): - self.args = args - self.kw = kw - - def generate(self, klass): - return klass(*self.args, **self.kw) - - -class Instance(ClassBasedTraitType): - """A trait whose value must be an instance of a specified class. - - The value can also be an instance of a subclass of the specified class. - """ - - def __init__(self, klass=None, args=None, kw=None, - allow_none=True, **metadata ): - """Construct an Instance trait. - - This trait allows values that are instances of a particular - class or its sublclasses. Our implementation is quite different - from that of enthough.traits as we don't allow instances to be used - for klass and we handle the ``args`` and ``kw`` arguments differently. - - Parameters - ---------- - klass : class, str - The class that forms the basis for the trait. Class names - can also be specified as strings, like 'foo.bar.Bar'. - args : tuple - Positional arguments for generating the default value. - kw : dict - Keyword arguments for generating the default value. - allow_none : bool - Indicates whether None is allowed as a value. - - Default Value - ------------- - If both ``args`` and ``kw`` are None, then the default value is None. - If ``args`` is a tuple and ``kw`` is a dict, then the default is - created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is - not (but not both), None is replace by ``()`` or ``{}``. - """ - - self._allow_none = allow_none - - if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))): - raise TraitError('The klass argument must be a class' - ' you gave: %r' % klass) - self.klass = klass - - # self.klass is a class, so handle default_value - if args is None and kw is None: - default_value = None - else: - if args is None: - # kw is not None - args = () - elif kw is None: - # args is not None - kw = {} - - if not isinstance(kw, dict): - raise TraitError("The 'kw' argument must be a dict or None.") - if not isinstance(args, tuple): - raise TraitError("The 'args' argument must be a tuple or None.") - - default_value = DefaultValueGenerator(*args, **kw) - - super(Instance, self).__init__(default_value, **metadata) - - def validate(self, obj, value): - if value is None: - if self._allow_none: - return value - self.error(obj, value) - - if isinstance(value, self.klass): - return value - else: - self.error(obj, value) - - def info(self): - if isinstance(self.klass, basestring): - klass = self.klass - else: - klass = self.klass.__name__ - result = class_of(klass) - if self._allow_none: - return result + ' or None' - - return result - - def instance_init(self, obj): - self._resolve_classes() - super(Instance, self).instance_init(obj) - - def _resolve_classes(self): - if isinstance(self.klass, basestring): - self.klass = import_item(self.klass) - - def get_default_value(self): - """Instantiate a default value instance. - - This is called when the containing HasTraits classes' - :meth:`__new__` method is called to ensure that a unique instance - is created for each HasTraits instance. - """ - dv = self.default_value - if isinstance(dv, DefaultValueGenerator): - return dv.generate(self.klass) - else: - return dv - - -class This(ClassBasedTraitType): - """A trait for instances of the class containing this trait. - - Because how how and when class bodies are executed, the ``This`` - trait can only have a default value of None. This, and because we - always validate default values, ``allow_none`` is *always* true. - """ - - info_text = 'an instance of the same type as the receiver or None' - - def __init__(self, **metadata): - super(This, self).__init__(None, **metadata) - - def validate(self, obj, value): - # What if value is a superclass of obj.__class__? This is - # complicated if it was the superclass that defined the This - # trait. - if isinstance(value, self.this_class) or (value is None): - return value - else: - self.error(obj, value) - - -#----------------------------------------------------------------------------- -# Basic TraitTypes implementations/subclasses -#----------------------------------------------------------------------------- - - -class Any(TraitType): - default_value = None - info_text = 'any value' - - -class Int(TraitType): - """An int trait.""" - - default_value = 0 - info_text = 'an int' - - def validate(self, obj, value): - if isinstance(value, int): - return value - self.error(obj, value) - -class CInt(Int): - """A casting version of the int trait.""" - - def validate(self, obj, value): - try: - return int(value) - except: - self.error(obj, value) - -if py3compat.PY3: - Long, CLong = Int, CInt - Integer = Int -else: - class Long(TraitType): - """A long integer trait.""" - - default_value = 0L - info_text = 'a long' - - def validate(self, obj, value): - if isinstance(value, long): - return value - if isinstance(value, int): - return long(value) - self.error(obj, value) - - - class CLong(Long): - """A casting version of the long integer trait.""" - - def validate(self, obj, value): - try: - return long(value) - except: - self.error(obj, value) - - class Integer(TraitType): - """An integer trait. - - Longs that are unnecessary (<= sys.maxint) are cast to ints.""" - - default_value = 0 - info_text = 'an integer' - - def validate(self, obj, value): - if isinstance(value, int): - return value - if isinstance(value, long): - # downcast longs that fit in int: - # note that int(n > sys.maxint) returns a long, so - # we don't need a condition on this cast - return int(value) - if sys.platform == "cli": - from System import Int64 - if isinstance(value, Int64): - return int(value) - self.error(obj, value) - - -class Float(TraitType): - """A float trait.""" - - default_value = 0.0 - info_text = 'a float' - - def validate(self, obj, value): - if isinstance(value, float): - return value - if isinstance(value, int): - return float(value) - self.error(obj, value) - - -class CFloat(Float): - """A casting version of the float trait.""" - - def validate(self, obj, value): - try: - return float(value) - except: - self.error(obj, value) - -class Complex(TraitType): - """A trait for complex numbers.""" - - default_value = 0.0 + 0.0j - info_text = 'a complex number' - - def validate(self, obj, value): - if isinstance(value, complex): - return value - if isinstance(value, (float, int)): - return complex(value) - self.error(obj, value) - - -class CComplex(Complex): - """A casting version of the complex number trait.""" - - def validate (self, obj, value): - try: - return complex(value) - except: - self.error(obj, value) - -# We should always be explicit about whether we're using bytes or unicode, both -# for Python 3 conversion and for reliable unicode behaviour on Python 2. So -# we don't have a Str type. -class Bytes(TraitType): - """A trait for byte strings.""" - - default_value = b'' - info_text = 'a string' - - def validate(self, obj, value): - if isinstance(value, bytes): - return value - self.error(obj, value) - - -class CBytes(Bytes): - """A casting version of the byte string trait.""" - - def validate(self, obj, value): - try: - return bytes(value) - except: - self.error(obj, value) - - -class Unicode(TraitType): - """A trait for unicode strings.""" - - default_value = u'' - info_text = 'a unicode string' - - def validate(self, obj, value): - if isinstance(value, unicode): - return value - if isinstance(value, bytes): - return unicode(value) - self.error(obj, value) - - -class CUnicode(Unicode): - """A casting version of the unicode trait.""" - - def validate(self, obj, value): - try: - return unicode(value) - except: - self.error(obj, value) - - -class ObjectName(TraitType): - """A string holding a valid object name in this version of Python. - - This does not check that the name exists in any scope.""" - info_text = "a valid object identifier in Python" - - if py3compat.PY3: - # Python 3: - coerce_str = staticmethod(lambda _,s: s) - - else: - # Python 2: - def coerce_str(self, obj, value): - "In Python 2, coerce ascii-only unicode to str" - if isinstance(value, unicode): - try: - return str(value) - except UnicodeEncodeError: - self.error(obj, value) - return value - - def validate(self, obj, value): - value = self.coerce_str(obj, value) - - if isinstance(value, str) and py3compat.isidentifier(value): - return value - self.error(obj, value) - -class DottedObjectName(ObjectName): - """A string holding a valid dotted object name in Python, such as A.b3._c""" - def validate(self, obj, value): - value = self.coerce_str(obj, value) - - if isinstance(value, str) and py3compat.isidentifier(value, dotted=True): - return value - self.error(obj, value) - - -class Bool(TraitType): - """A boolean (True, False) trait.""" - - default_value = False - info_text = 'a boolean' - - def validate(self, obj, value): - if isinstance(value, bool): - return value - self.error(obj, value) - - -class CBool(Bool): - """A casting version of the boolean trait.""" - - def validate(self, obj, value): - try: - return bool(value) - except: - self.error(obj, value) - - -class Enum(TraitType): - """An enum that whose value must be in a given sequence.""" - - def __init__(self, values, default_value=None, allow_none=True, **metadata): - self.values = values - self._allow_none = allow_none - super(Enum, self).__init__(default_value, **metadata) - - def validate(self, obj, value): - if value is None: - if self._allow_none: - return value - - if value in self.values: - return value - self.error(obj, value) - - def info(self): - """ Returns a description of the trait.""" - result = 'any of ' + repr(self.values) - if self._allow_none: - return result + ' or None' - return result - -class CaselessStrEnum(Enum): - """An enum of strings that are caseless in validate.""" - - def validate(self, obj, value): - if value is None: - if self._allow_none: - return value - - if not isinstance(value, basestring): - self.error(obj, value) - - for v in self.values: - if v.lower() == value.lower(): - return v - self.error(obj, value) - -class Container(Instance): - """An instance of a container (list, set, etc.) - - To be subclassed by overriding klass. - """ - klass = None - _valid_defaults = SequenceTypes - _trait = None - - def __init__(self, trait=None, default_value=None, allow_none=True, - **metadata): - """Create a container trait type from a list, set, or tuple. - - The default value is created by doing ``List(default_value)``, - which creates a copy of the ``default_value``. - - ``trait`` can be specified, which restricts the type of elements - in the container to that TraitType. - - If only one arg is given and it is not a Trait, it is taken as - ``default_value``: - - ``c = List([1,2,3])`` - - Parameters - ---------- - - trait : TraitType [ optional ] - the type for restricting the contents of the Container. If unspecified, - types are not checked. - - default_value : SequenceType [ optional ] - The default value for the Trait. Must be list/tuple/set, and - will be cast to the container type. - - allow_none : Bool [ default True ] - Whether to allow the value to be None - - **metadata : any - further keys for extensions to the Trait (e.g. config) - - """ - # allow List([values]): - if default_value is None and not is_trait(trait): - default_value = trait - trait = None - - if default_value is None: - args = () - elif isinstance(default_value, self._valid_defaults): - args = (default_value,) - else: - raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) - - if is_trait(trait): - self._trait = trait() if isinstance(trait, type) else trait - self._trait.name = 'element' - elif trait is not None: - raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait)) - - super(Container,self).__init__(klass=self.klass, args=args, - allow_none=allow_none, **metadata) - - def element_error(self, obj, element, validator): - e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \ - % (self.name, class_of(obj), validator.info(), repr_type(element)) - raise TraitError(e) - - def validate(self, obj, value): - value = super(Container, self).validate(obj, value) - if value is None: - return value - - value = self.validate_elements(obj, value) - - return value - - def validate_elements(self, obj, value): - validated = [] - if self._trait is None or isinstance(self._trait, Any): - return value - for v in value: - try: - v = self._trait.validate(obj, v) - except TraitError: - self.element_error(obj, v, self._trait) - else: - validated.append(v) - return self.klass(validated) - - -class List(Container): - """An instance of a Python list.""" - klass = list - - def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, - allow_none=True, **metadata): - """Create a List trait type from a list, set, or tuple. - - The default value is created by doing ``List(default_value)``, - which creates a copy of the ``default_value``. - - ``trait`` can be specified, which restricts the type of elements - in the container to that TraitType. - - If only one arg is given and it is not a Trait, it is taken as - ``default_value``: - - ``c = List([1,2,3])`` - - Parameters - ---------- - - trait : TraitType [ optional ] - the type for restricting the contents of the Container. If unspecified, - types are not checked. - - default_value : SequenceType [ optional ] - The default value for the Trait. Must be list/tuple/set, and - will be cast to the container type. - - minlen : Int [ default 0 ] - The minimum length of the input list - - maxlen : Int [ default sys.maxsize ] - The maximum length of the input list - - allow_none : Bool [ default True ] - Whether to allow the value to be None - - **metadata : any - further keys for extensions to the Trait (e.g. config) - - """ - self._minlen = minlen - self._maxlen = maxlen - super(List, self).__init__(trait=trait, default_value=default_value, - allow_none=allow_none, **metadata) - - def length_error(self, obj, value): - e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \ - % (self.name, class_of(obj), self._minlen, self._maxlen, value) - raise TraitError(e) - - def validate_elements(self, obj, value): - length = len(value) - if length < self._minlen or length > self._maxlen: - self.length_error(obj, value) - - return super(List, self).validate_elements(obj, value) - - -class Set(Container): - """An instance of a Python set.""" - klass = set - -class Tuple(Container): - """An instance of a Python tuple.""" - klass = tuple - - def __init__(self, *traits, **metadata): - """Tuple(*traits, default_value=None, allow_none=True, **medatata) - - Create a tuple from a list, set, or tuple. - - Create a fixed-type tuple with Traits: - - ``t = Tuple(Int, Str, CStr)`` - - would be length 3, with Int,Str,CStr for each element. - - If only one arg is given and it is not a Trait, it is taken as - default_value: - - ``t = Tuple((1,2,3))`` - - Otherwise, ``default_value`` *must* be specified by keyword. - - Parameters - ---------- - - *traits : TraitTypes [ optional ] - the tsype for restricting the contents of the Tuple. If unspecified, - types are not checked. If specified, then each positional argument - corresponds to an element of the tuple. Tuples defined with traits - are of fixed length. - - default_value : SequenceType [ optional ] - The default value for the Tuple. Must be list/tuple/set, and - will be cast to a tuple. If `traits` are specified, the - `default_value` must conform to the shape and type they specify. - - allow_none : Bool [ default True ] - Whether to allow the value to be None - - **metadata : any - further keys for extensions to the Trait (e.g. config) - - """ - default_value = metadata.pop('default_value', None) - allow_none = metadata.pop('allow_none', True) - - # allow Tuple((values,)): - if len(traits) == 1 and default_value is None and not is_trait(traits[0]): - default_value = traits[0] - traits = () - - if default_value is None: - args = () - elif isinstance(default_value, self._valid_defaults): - args = (default_value,) - else: - raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) - - self._traits = [] - for trait in traits: - t = trait() if isinstance(trait, type) else trait - t.name = 'element' - self._traits.append(t) - - if self._traits and default_value is None: - # don't allow default to be an empty container if length is specified - args = None - super(Container,self).__init__(klass=self.klass, args=args, - allow_none=allow_none, **metadata) - - def validate_elements(self, obj, value): - if not self._traits: - # nothing to validate - return value - if len(value) != len(self._traits): - e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \ - % (self.name, class_of(obj), len(self._traits), repr_type(value)) - raise TraitError(e) - - validated = [] - for t,v in zip(self._traits, value): - try: - v = t.validate(obj, v) - except TraitError: - self.element_error(obj, v, t) - else: - validated.append(v) - return tuple(validated) - - -class Dict(Instance): - """An instance of a Python dict.""" - - def __init__(self, default_value=None, allow_none=True, **metadata): - """Create a dict trait type from a dict. - - The default value is created by doing ``dict(default_value)``, - which creates a copy of the ``default_value``. - """ - if default_value is None: - args = ((),) - elif isinstance(default_value, dict): - args = (default_value,) - elif isinstance(default_value, SequenceTypes): - args = (default_value,) - else: - raise TypeError('default value of Dict was %s' % default_value) - - super(Dict,self).__init__(klass=dict, args=args, - allow_none=allow_none, **metadata) - -class TCPAddress(TraitType): - """A trait for an (ip, port) tuple. - - This allows for both IPv4 IP addresses as well as hostnames. - """ - - default_value = ('127.0.0.1', 0) - info_text = 'an (ip, port) tuple' - - def validate(self, obj, value): - if isinstance(value, tuple): - if len(value) == 2: - if isinstance(value[0], basestring) and isinstance(value[1], int): - port = value[1] - if port >= 0 and port <= 65535: - return value - self.error(obj, value) - -class CRegExp(TraitType): - """A casting compiled regular expression trait. - - Accepts both strings and compiled regular expressions. The resulting - attribute will be a compiled regular expression.""" - - info_text = 'a regular expression' - - def validate(self, obj, value): - try: - return re.compile(value) - except: - self.error(obj, value) diff --git a/converters/base.py b/converters/base.py index 2bf4dce..a1b0ecb 100755 --- a/converters/base.py +++ b/converters/base.py @@ -131,7 +131,7 @@ class Converter(object): user_preamble = None raw_as_verbatim = False - def __init__(self, infile, highlight_source=True, exclude=[]): + def __init__(self, infile, highlight_source=True, exclude=[], **kw): # N.B. Initialized in the same order as defined above. Please try to # keep in this way for readability's sake. self.exclude_cells = exclude diff --git a/nbconvert.py b/nbconvert.py index aba51a3..094a514 100755 --- a/nbconvert.py +++ b/nbconvert.py @@ -15,6 +15,13 @@ from __future__ import print_function # From IPython from IPython.external import argparse +# All the stuff needed for the configurable things +from IPython.config.application import Application, catch_config_error +from IPython.config.configurable import Configurable +from IPython.config.loader import Config, ConfigFileNotFound +from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, CaselessStrEnum + + # local from converters.html import ConverterHTML from converters.markdown import ConverterMarkdown @@ -44,44 +51,95 @@ default_format = 'rst' known_formats = ', '.join([key + " (default)" if key == default_format else key for key in converters]) +class NbconvertApp(Application): -def main(infile, format='rst', preamble=None, exclude=[], - highlight_source=True): - """Convert a notebook to html in one step""" - try: - ConverterClass = converters[format] - except KeyError: - raise SystemExit("Unknown format '%s', " % format + - "known formats are: " + known_formats) + name = Unicode('thisIsNbconvertApp',config=True) + + fmt = CaselessStrEnum(converters.keys(), + default_value='rst', + config=True, + help="Supported conversion format") + + preamble = Unicode("" , + config=True, + help="Path to a user-specified preamble file") + + highlight = Bool(True, + config=True, + help="Enable syntax highlighting for code blocks.") + + exclude = List( [], + config=True, + help = 'list of cells to exclude while converting') + + infile = Unicode("", config=True) converter = ConverterClass(infile, highlight_source=highlight_source, exclude=exclude) converter.render() + + aliases = { + 'format':'NbconvertApp.fmt', + 'highlight':'NbconvertApp.highlight', + 'preamble':'NbconvertApp.preamble', + 'infile' : 'NbconvertApp.infile' + } + + def __init__(self, **kwargs): + super(NbconvertApp, self).__init__(**kwargs) + + def initialize(self, argv=None): + # don't hook up crash handler before parsing command-line + self.parse_command_line(argv) + cl_config = self.config + print(self.config) + self.update_config(cl_config) + #self.init_crash_handler() + #self.foo = Cnf(config=self.config) + #if self.subapp is not None: + # stop here if subapp is taking over + #return + #cl_config = self.config + #self.init_profile_dir() + #self.init_config_files() + #self.load_config_file() + # enforce cl-opts override configfile opts: + #self.update_config(cl_config) + + + def run(self): + """Convert a notebook to html in one step""" + ConverterClass = converters[self.fmt] + + converter = ConverterClass(self.infile, highlight_source=self.highlight, preamble=self.preamble, exclude=self.exclude) + converter.render() + +def main(): + """Convert a notebook to html in one step""" + app = NbconvertApp.instance() + print(app.classes) + app.initialize() + app.start() + app.run() #----------------------------------------------------------------------------- # Script main #----------------------------------------------------------------------------- if __name__ == '__main__': - parser = argparse.ArgumentParser(description=__doc__, - formatter_class=argparse.RawTextHelpFormatter) + #parser = argparse.ArgumentParser(description=__doc__, + # formatter_class=argparse.RawTextHelpFormatter) # TODO: consider passing file like object around, rather than filenames # would allow us to process stdin, or even http streams #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), # default=sys.stdin) #Require a filename as a positional argument - parser.add_argument('infile', nargs=1) - parser.add_argument('-f', '--format', default='rst', - help='Output format. Supported formats: \n' + - known_formats) - parser.add_argument('-p', '--preamble', - help='Path to a user-specified preamble file') - parser.add_argument('-e', '--exclude', default='', - help='Comma-separated list of cells to exclude') - parser.add_argument('-H', '--no-highlighting', action='store_false', - help='Disable syntax highlighting for code blocks.') - args = parser.parse_args() - exclude_cells = [s.strip() for s in args.exclude.split(',')] - - main(infile=args.infile[0], format=args.format, preamble=args.preamble, - exclude=exclude_cells, highlight_source=args.no_highlighting) + #parser.add_argument('infile', nargs=1) + #parser.add_argument('-e', '--exclude', default='', + # help='Comma-separated list of cells to exclude') + #parser.add_argument('-H', '--no-highlighting', action='store_false', + # help='Disable syntax highlighting for code blocks.') + #args = parser.parse_args() + #exclude_cells = [s.strip() for s in args.exclude.split(',')] + + main()