# encoding: utf-8 """Mix of ConfigObj and Struct-like access. Provides: - Coupling a Struct object to a ConfigObj one, so that changes to the Traited instance propagate back into the ConfigObj. - A declarative interface for describing configurations that automatically maps to valid ConfigObj representations. - From these descriptions, valid .conf files can be auto-generated, with class docstrings and traits information used for initial auto-documentation. - Hierarchical inclusion of files, so that a base config can be overridden only in specific spots. Notes: The file creation policy is: 1. Creating a SConfigManager(FooConfig,'missingfile.conf') will work fine, and 'missingfile.conf' will be created empty. 2. Creating SConfigManager(FooConfig,'OKfile.conf') where OKfile.conf has include = 'missingfile.conf' conks out with IOError. My rationale is that creating top-level empty files is a common and reasonable need, but that having invalid include statements should raise an error right away, so people know immediately that their files have gone stale. TODO: - Turn the currently interactive tests into proper doc/unit tests. Complete docstrings. - Write the real ipython1 config system using this. That one is more complicated than either the MPL one or the fake 'ipythontest' that I wrote here, and it requires solving the issue of declaring references to other objects inside the config files. - [Low priority] Write a custom TraitsUI view so that hierarchical configurations provide nicer interactive editing. The automatic system is remarkably good, but for very complex configurations having a nicely organized view would be nice. """ __docformat__ = "restructuredtext en" __license__ = 'BSD' #------------------------------------------------------------------------------- # Copyright (C) 2008 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 #------------------------------------------------------------------------------- ############################################################################ # Stdlib imports ############################################################################ from cStringIO import StringIO from inspect import isclass import os import textwrap ############################################################################ # External imports ############################################################################ from IPython.external import configobj ############################################################################ # Utility functions ############################################################################ def get_split_ind(seq, N): """seq is a list of words. Return the index into seq such that len(' '.join(seq[:ind])<=N """ sLen = 0 # todo: use Alex's xrange pattern from the cbook for efficiency for (word, ind) in zip(seq, range(len(seq))): sLen += len(word) + 1 # +1 to account for the len(' ') if sLen>=N: return ind return len(seq) def wrap(prefix, text, cols, max_lines=6): """'wrap text with prefix at length cols""" pad = ' '*len(prefix.expandtabs()) available = cols - len(pad) seq = text.split(' ') Nseq = len(seq) ind = 0 lines = [] while ind num_lines-abbr_end-1: ret += pad + ' '.join(lines[i]) + '\n' else: if not lines_skipped: lines_skipped = True ret += ' <...snipped %d lines...> \n' % (num_lines-max_lines) # for line in lines[1:]: # ret += pad + ' '.join(line) + '\n' return ret[:-1] def dedent(txt): """A modified version of textwrap.dedent, specialized for docstrings. This version doesn't get confused by the first line of text having inconsistent indentation from the rest, which happens a lot in docstrings. :Examples: >>> s = ''' ... First line. ... More... ... End''' >>> print dedent(s) First line. More... End >>> s = '''First line ... More... ... End''' >>> print dedent(s) First line More... End """ out = [textwrap.dedent(t) for t in txt.split('\n',1) if t and not t.isspace()] return '\n'.join(out) def comment(strng,indent=''): """return an input string, commented out""" template = indent + '# %s' lines = [template % s for s in strng.splitlines(True)] return ''.join(lines) def configobj2str(cobj): """Dump a Configobj instance to a string.""" outstr = StringIO() cobj.write(outstr) return outstr.getvalue() def get_config_filename(conf): """Find the filename attribute of a ConfigObj given a sub-section object. """ depth = conf.depth for d in range(depth): conf = conf.parent return conf.filename def sconf2File(sconf,fname,force=False): """Write a SConfig instance to a given filename. :Keywords: force : bool (False) If true, force writing even if the file exists. """ if os.path.isfile(fname) and not force: raise IOError("File %s already exists, use force=True to overwrite" % fname) txt = repr(sconf) fobj = open(fname,'w') fobj.write(txt) fobj.close() def filter_scalars(sc): """ input sc MUST be sorted!!!""" scalars = [] maxi = len(sc)-1 i = 0 while i