[Checkins] SVN: zc.buildout/branches/dev/ - Variable substitutions now reflect option data written by recipes.

Jim Fulton jim at zope.com
Tue Nov 28 17:05:09 EST 2006


Log message for revision 71321:
  - Variable substitutions now reflect option data written by recipes.
  
  - A part referenced by a part in a part list is now added to the part
    list before the referencing part.  This means that you can omit
    parts from the parts list if they are referenced by other parts.
  
  XXX need tests for this.
  
  - Removed support ConfigParser-style variable substitutions
    (e.g. %(foo)s). Only the string-template style of variable
    (e.g. ${section:option}) substitutions will be supported.
    Supporting both violates "there's only one way to do it".
  
  - Deprecated the buildout-section extendedBy option.
  

Changed:
  U   zc.buildout/branches/dev/CHANGES.txt
  U   zc.buildout/branches/dev/src/zc/buildout/buildout.py
  U   zc.buildout/branches/dev/src/zc/buildout/buildout.txt
  U   zc.buildout/branches/dev/src/zc/buildout/tests.py

-=-
Modified: zc.buildout/branches/dev/CHANGES.txt
===================================================================
--- zc.buildout/branches/dev/CHANGES.txt	2006-11-28 14:01:56 UTC (rev 71320)
+++ zc.buildout/branches/dev/CHANGES.txt	2006-11-28 22:05:09 UTC (rev 71321)
@@ -20,9 +20,27 @@
 Change History
 **************
 
-1.0.0b12 (2006-10-?)
+1.0.0b13 (2006-11-28)
 =====================
 
+Feature Changes
+---------------
+
+- Variable substitutions now reflect option data written by recipes.
+
+- A part referenced by a part in a part list is now added to the part
+  list before the referencing part.  This means that you can omit
+  parts from the parts list if they are referenced by other parts.
+
+  XXX need tests for this.
+
+- Removed support ConfigParser-style variable substitutions
+  (e.g. %(foo)s). Only the string-template style of variable
+  (e.g. ${section:option}) substitutions will be supported.
+  Supporting both violates "there's only one way to do it".
+
+- Deprecated the buildout-section extendedBy option.
+
 Bugs Fixed
 ----------
 

Modified: zc.buildout/branches/dev/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/branches/dev/src/zc/buildout/buildout.py	2006-11-28 14:01:56 UTC (rev 71320)
+++ zc.buildout/branches/dev/src/zc/buildout/buildout.py	2006-11-28 22:05:09 UTC (rev 71321)
@@ -25,6 +25,7 @@
 import sys
 import tempfile
 import ConfigParser
+import UserDict
 
 import pkg_resources
 import zc.buildout
@@ -42,31 +43,12 @@
     """A required section is missinh
     """
 
-class Options(dict):
+    def __str__(self):
+        return "The referenced section, %r, was not defined." % self[0]
 
-    def __init__(self, buildout, section, data):
-        self.buildout = buildout
-        self.section = section
-        super(Options, self).__init__(data)
 
-    def __getitem__(self, option):
-        try:
-            return super(Options, self).__getitem__(option)
-        except KeyError:
-            raise MissingOption("Missing option: %s:%s"
-                                % (self.section, option))
+class Buildout(UserDict.DictMixin):
 
-    # XXX need test
-    def __setitem__(self, option, value):
-        if not isinstance(value, str):
-            raise TypeError('Option values must be strings', value)
-        super(Options, self).__setitem__(option, value)
-
-    def copy(self):
-        return Options(self.buildout, self.section, self)
-
-class Buildout(dict):
-
     def __init__(self, config_file, cloptions, windows_restart=False):
         config_file = os.path.abspath(config_file)
         self._config_file = config_file
@@ -75,8 +57,6 @@
             print 'Warning: creating', config_file
             open(config_file, 'w').write('[buildout]\nparts = \n')
 
-        super(Buildout, self).__init__()
-
         # default options
         data = dict(buildout={
             'directory': os.path.dirname(config_file),
@@ -110,19 +90,9 @@
             options[option] = value
                 # The egg dire
 
-        # do substitutions
-        converted = {}
-        for section, options in data.iteritems():
-            for option, value in options.iteritems():
-                if '$' in value:
-                    value = self._dosubs(section, option, value,
-                                         data, converted, [])
-                    options[option] = value
-                converted[(section, option)] = value
-
-        # copy data into self:
-        for section, options in data.iteritems():
-            self[section] = Options(self, section, options)
+        self._raw = data
+        self._data = {}
+        self._parts = []
         
         # initialize some attrs and buildout directories.
         options = self['buildout']
@@ -140,73 +110,12 @@
 
         self._setup_logging()
 
-    def _dosubs(self, section, option, value, data, converted, seen):
-        key = section, option
-        r = converted.get(key)
-        if r is not None:
-            return r
-        if key in seen:
-            raise zc.buildout.UserError(
-                "Circular reference in substitutions.\n"
-                "We're evaluating %s\nand are referencing: %s.\n"
-                % (", ".join([":".join(k) for k in seen]),
-                   ":".join(key)
-                   )
-                )
-        seen.append(key)
-        value = '$$'.join([self._dosubs_esc(s, data, converted, seen)
-                           for s in value.split('$$')
-                           ])
-        seen.pop()
-        return value
+        offline = options.get('offline', 'false')
+        if offline not in ('true', 'false'):
+            self._error('Invalid value for offline option: %s', offline)
+        options['offline'] = offline
 
-    _template_split = re.compile('([$]{[^}]*})').split
-    _simple = re.compile('[-a-zA-Z0-9 ._]+$').match
-    _valid = re.compile('[-a-zA-Z0-9 ._]+:[-a-zA-Z0-9 ._]+$').match
-    def _dosubs_esc(self, value, data, converted, seen):
-        value = self._template_split(value)
-        subs = []
-        for ref in value[1::2]:
-            s = tuple(ref[2:-1].split(':'))
-            if not self._valid(ref):
-                if len(s) < 2:
-                    raise zc.buildout.UserError("The substitution, %s,\n"
-                                                "doesn't contain a colon."
-                                                % ref)
-                if len(s) > 2:
-                    raise zc.buildout.UserError("The substitution, %s,\n"
-                                                "has too many colons."
-                                                % ref)
-                if not self._simple(s[0]):
-                    raise zc.buildout.UserError(
-                        "The section name in substitution, %s,\n"
-                        "has invalid characters."
-                        % ref)
-                if not self._simple(s[1]):
-                    raise zc.buildout.UserError(
-                        "The option name in substitution, %s,\n"
-                        "has invalid characters."
-                        % ref)
-                
-            v = converted.get(s)
-            if v is None:
-                options = data.get(s[0])
-                if options is None:
-                    raise MissingSection(
-                        "Referenced section does not exist", s[0])
-                v = options.get(s[1])
-                if v is None:
-                    raise MissingOption("Referenced option does not exist:",
-                                        *s)
-                if '$' in v:
-                    v = self._dosubs(s[0], s[1], v, data, converted, seen)
-                    options[s[1]] = v
-                converted[s] = v
-            subs.append(v)
-        subs.append('')
 
-        return ''.join([''.join(v) for v in zip(value[::2], subs)])
-
     def _buildout_path(self, *names):
         return os.path.join(self._buildout_dir, *names)
 
@@ -273,21 +182,35 @@
         # If install_parts is given, then they must be listed in parts
         # and we don't uninstall anything. Otherwise, we install
         # the configured parts and uninstall anything else.
+
+        # Why?
+        
         if install_parts:
-            extra = [p for p in install_parts if p not in conf_parts]
-            if extra:
-                self._error(
-                    'Invalid install parts: %s.\n'
-                    'Install parts must be listed in the configuration.',
-                    ' '.join(extra))
+##             extra = [p for p in install_parts if p not in conf_parts]
+##             if extra:
+##                 self._error(
+##                     'Invalid install parts: %s.\n'
+##                     'Install parts must be listed in the configuration.',
+##                     ' '.join(extra))
             uninstall_missing = False
         else:
             install_parts = conf_parts
             uninstall_missing = True
 
-        # load recipes
-        recipes = self._load_recipes(install_parts)
+        # load and initialize recipes
+        [self[part]['recipe'] for part in install_parts]
+        install_parts = self._parts
 
+        if self._log_level <= logging.DEBUG:
+            sections = list(self)
+            sections.sort()
+            print    
+            print 'Configuration data:'
+            for section in self._data:
+                _save_options(section, self[section], sys.stdout)
+            print    
+
+
         # compute new part recipe signatures
         self._compute_part_signatures(install_parts)
 
@@ -339,19 +262,20 @@
             for part in install_parts:
                 signature = self[part].pop('__buildout_signature__')
                 saved_options = self[part].copy()
+                recipe = self[part].recipe
                 if part in installed_parts:
                     self._logger.info('Updating %s', part)
                     old_options = installed_part_options[part]
                     old_installed_files = old_options['__buildout_installed__']
                     try:
-                        update = recipes[part].update
+                        update = recipe.update
                     except AttributeError:
-                        update = recipes[part].install
+                        update = recipe.install
                         self._logger.warning(
                             "The recipe for %s doesn't define an update "
                             "method. Using it's install method",
                             part)
-                        
+
                     try:
                         installed_files = update()
                     except:
@@ -364,7 +288,7 @@
 
                 else:
                     self._logger.info('Installing %s', part)
-                    installed_files = recipes[part].install()
+                    installed_files = recipe.install()
                     if installed_files is None:
                         self._logger.warning(
                             "The %s install returned None.  A path or "
@@ -490,87 +414,28 @@
                 self._logger.warning(
                     "Unexpected entry, %s, in develop-eggs directory", f)
 
-    def _load_recipes(self, parts):
-        recipes = {}
-        if not parts:
-            return recipes
-        
-        recipes_requirements = []
-        pkg_resources.working_set.add_entry(
-            self['buildout']['develop-eggs-directory'])
-        pkg_resources.working_set.add_entry(self['buildout']['eggs-directory'])
-
-        # Gather requirements
-        for part in parts:
-            options = self.get(part)
-            if options is None:
-                raise MissingSection("No section was specified for part", part)
-
-            recipe, entry = self._recipe(part, options)
-            if recipe not in recipes_requirements:
-                recipes_requirements.append(recipe)
-
-        # Install the recipe distros
-        offline = self['buildout'].get('offline', 'false')
-        if offline not in ('true', 'false'):
-            self._error('Invalid value for offline option: %s', offline)
-            
-        if offline == 'false':
-            dest = self['buildout']['eggs-directory']
-        else:
-            dest = None
-
-        ws = zc.buildout.easy_install.install(
-            recipes_requirements, dest,
-            links=self._links,
-            index=self['buildout'].get('index'),
-            path=[self['buildout']['develop-eggs-directory'],
-                  self['buildout']['eggs-directory'],
-                  ],
-            working_set=pkg_resources.working_set,
-            )
-
-        # instantiate the recipes
-        for part in parts:
-            options = self[part]
-            recipe, entry = self._recipe(part, options)
-            recipe_class = pkg_resources.load_entry_point(
-                recipe, 'zc.buildout', entry)
-            recipes[part] = recipe_class(self, part, options)
-        
-        return recipes
-
     def _compute_part_signatures(self, parts):
         # Compute recipe signature and add to options
         for part in parts:
             options = self.get(part)
             if options is None:
                 options = self[part] = {}
-            recipe, entry = self._recipe(part, options)
+            recipe, entry = _recipe(options)
             req = pkg_resources.Requirement.parse(recipe)
             sig = _dists_sig(pkg_resources.working_set.resolve([req]))
             options['__buildout_signature__'] = ' '.join(sig)
 
-    def _recipe(self, part, options):
-        recipe = options['recipe']
-        if ':' in recipe:
-            recipe, entry = recipe.split(':')
-        else:
-            entry = 'default'
-
-        return recipe, entry
-
     def _read_installed_part_options(self):
         old = self._installed_path()
         if os.path.isfile(old):
-            parser = ConfigParser.SafeConfigParser(_spacey_defaults)
+            parser = ConfigParser.RawConfigParser(_spacey_defaults)
             parser.optionxform = lambda s: s
             parser.read(old)
             return dict([
                 (section,
                  Options(self, section,
-                         [item for item in parser.items(section)
-                          if item[0] not in _spacey_defaults]
+                         dict([item for item in parser.items(section)
+                               if item[0] not in _spacey_defaults])
                          )
                  )
                 for section in parser.sections()])
@@ -592,7 +457,7 @@
                 
     def _install(self, part):
         options = self[part]
-        recipe, entry = self._recipe(part, options)
+        recipe, entry = _recipe(options)
         recipe_class = pkg_resources.load_entry_point(
             recipe, 'zc.buildout', entry)
         installed = recipe_class(self, part, options).install()
@@ -642,14 +507,6 @@
         root_logger.setLevel(level)
         self._log_level = level
 
-        if level <= logging.DEBUG:
-            sections = list(self)
-            sections.sort()
-            print 'Configuration data:'
-            for section in sections:
-                _save_options(section, self[section], sys.stdout)
-            print    
-
     def _maybe_upgrade(self):
         # See if buildout or setuptools need to be upgraded.
         # If they do, do the upgrade and restart the buildout process.
@@ -758,8 +615,181 @@
             os.close(fd)
             os.remove(tsetup)
 
-    runsetup = setup # backward compat
+    runsetup = setup # backward compat.
+
+    def __getitem__(self, section):
+        try:
+            return self._data[section]
+        except KeyError:
+            pass
+
+        try:
+            data = self._raw[section]
+        except KeyError:
+            raise MissingSection(section)
+
+        options = Options(self, section, data)
+        self._data[section] = options
+        options._initialize()
+        return options          
+
+    def __setitem__(self, key, value):
+        raise NotImplementedError('__setitem__')
+
+    def __delitem__(self, key):
+        raise NotImplementedError('__delitem__')
+
+    def keys(self):
+        return self._raw.keys()
+
+    def __iter__(self):
+        return iter(self._raw)
+
+class Options(UserDict.DictMixin):
+
+    def __init__(self, buildout, section, data):
+        self.buildout = buildout
+        self.name = section
+        self._raw = data
+        self._data = {}
+
+    def _initialize(self):
+        # force substitutions
+        for k in self._raw:
+            self.get(k)
+
+        recipe = self.get('recipe')
+        if not recipe:
+            return
         
+        reqs, entry = _recipe(self._data)
+        req = pkg_resources.Requirement.parse(reqs)
+        buildout = self.buildout
+        
+        if pkg_resources.working_set.find(req) is None:
+            offline = buildout['buildout']['offline'] == 'true'
+            if offline:
+                dest = None
+                path = [buildout['buildout']['develop-eggs-directory'],
+                        buildout['buildout']['eggs-directory'],
+                        ]
+            else:
+                dest = buildout['buildout']['eggs-directory']
+                path = [buildout['buildout']['develop-eggs-directory']]
+            zc.buildout.easy_install.install(
+                [reqs], dest,
+                links=buildout._links,
+                index=buildout['buildout'].get('index'),
+                path=path,
+                working_set=pkg_resources.working_set,
+                )
+            
+        recipe_class = pkg_resources.load_entry_point(
+            req.project_name, 'zc.buildout', entry)
+
+        self.recipe = recipe_class(buildout, self.name, self)
+        buildout._parts.append(self.name)
+
+    def get(self, option, default=None, seen=None):
+        try:
+            return self._data[option]
+        except KeyError:
+            pass
+
+        v = self._raw.get(option)
+        if v is None:
+            return default
+
+        if '${' in v:
+            key = self.name, option
+            if seen is None:
+                seen = [key]
+            elif key in seen:
+                raise zc.buildout.UserError(
+                    "Circular reference in substitutions.\n"
+                    "We're evaluating %s\nand are referencing: %s.\n"
+                    % (", ".join([":".join(k) for k in seen]),
+                       ":".join(key)
+                       )
+                    )
+            else:
+                seen.append(key)
+            v = '$$'.join([self._sub(s, seen) for s in v.split('$$')])
+            seen.pop()
+
+        self._data[option] = v
+        return v
+
+    _template_split = re.compile('([$]{[^}]*})').split
+    _simple = re.compile('[-a-zA-Z0-9 ._]+$').match
+    _valid = re.compile('[-a-zA-Z0-9 ._]+:[-a-zA-Z0-9 ._]+$').match
+    def _sub(self, template, seen):
+        value = self._template_split(template)
+        subs = []
+        for ref in value[1::2]:
+            s = tuple(ref[2:-1].split(':'))
+            if not self._valid(ref):
+                if len(s) < 2:
+                    raise zc.buildout.UserError("The substitution, %s,\n"
+                                                "doesn't contain a colon."
+                                                % ref)
+                if len(s) > 2:
+                    raise zc.buildout.UserError("The substitution, %s,\n"
+                                                "has too many colons."
+                                                % ref)
+                if not self._simple(s[0]):
+                    raise zc.buildout.UserError(
+                        "The section name in substitution, %s,\n"
+                        "has invalid characters."
+                        % ref)
+                if not self._simple(s[1]):
+                    raise zc.buildout.UserError(
+                        "The option name in substitution, %s,\n"
+                        "has invalid characters."
+                        % ref)
+                
+            v = self.buildout[s[0]].get(s[1], None, seen)
+            if v is None:
+                raise MissingOption("Referenced option does not exist:", *s)
+            subs.append(v)
+        subs.append('')
+
+        return ''.join([''.join(v) for v in zip(value[::2], subs)])
+        
+    def __getitem__(self, key):
+        try:
+            return self._data[key]
+        except KeyError:
+            pass
+
+        v = self.get(key)
+        if v is None:
+            raise MissingOption("Missing option: %s:%s"
+                                % (self.name, key))
+        return v
+
+    def __setitem__(self, option, value):
+        if not isinstance(value, str):
+            raise TypeError('Option values must be strings', value)
+        self._data[option] = value
+
+    def __delitem__(self, key):
+        if key in self._raw:
+            del self._raw[key]
+            if key in self._data:
+                del self._data[key]
+        elif key in self._data:
+            del self._data[key]
+        else:
+            raise KeyError, key
+
+    def keys(self):
+        raw = self._raw
+        return list(self._raw) + [k for k in self._data if k not in raw]
+
+    def copy(self):
+        return dict([(k, self[k]) for k in self.keys()])
+        
 runsetup_template = """
 import sys
 sys.path.insert(0, %(setuptools)r)
@@ -832,7 +862,7 @@
 
     result = {}
 
-    parser = ConfigParser.SafeConfigParser()
+    parser = ConfigParser.RawConfigParser()
     parser.optionxform = lambda s: s
     parser.readfp(open(filename))
     extends = extended_by = None
@@ -850,6 +880,9 @@
             result = _update(_open(base, fname, seen), result)
 
     if extended_by:
+        self._logger.warn(
+            "The extendedBy option is deprecated.  Stop using it."
+            )
         for fname in extended_by.split():
             result = _update(result, _open(base, fname, seen))
 
@@ -887,6 +920,15 @@
             d1[section] = d2[section]
     return d1
 
+def _recipe(options):
+    recipe = options['recipe']
+    if ':' in recipe:
+        recipe, entry = recipe.split(':')
+    else:
+        entry = 'default'
+
+    return recipe, entry
+
 def _error(*message):
     sys.stderr.write('Error: ' + ' '.join(message) +'\n')
     sys.exit(1)

Modified: zc.buildout/branches/dev/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/branches/dev/src/zc/buildout/buildout.txt	2006-11-28 14:01:56 UTC (rev 71320)
+++ zc.buildout/branches/dev/src/zc/buildout/buildout.txt	2006-11-28 22:05:09 UTC (rev 71321)
@@ -418,9 +418,8 @@
 Variable substitutions
 ----------------------
 
-Buildout configuration files support two kinds of substitutions,
-standard ConfigParser substitutions, and string-template
-substitutions.  To illustrate this, we'll create an debug recipe to
+Buildout configuration files support variable substitution.
+To illustrate this, we'll create an debug recipe to
 allow us to see interactions with the buildout:
 
     >>> write(sample_buildout, 'recipes', 'debug.py', 
@@ -442,10 +441,9 @@
     ...     update = install
     ... """)
 
-In this example, we've used a simple base class that provides a
-boilerplate constructor.  This recipe doesn't actually create
-anything. The install method doesn't return anything, because it
-didn't create any files or directories.
+This recipe doesn't actually create anything. The install method
+doesn't return anything, because it didn't create any files or
+directories.
 
 We also have to update our setup script:
 
@@ -478,16 +476,11 @@
     ... [debug]
     ... recipe = recipes:debug
     ... File 1 = ${data-dir:path}/file
-    ... File 2 = %(File 1)s.out
-    ... File 3 = %(base)s/file3
-    ... File 4 = ${debug:File 3}/log
+    ... File 2 = ${debug:File 1}/log
     ...
     ... [data-dir]
     ... recipe = recipes:mkdir
     ... path = mydata
-    ...
-    ... [DEFAULT]
-    ... base = var
     ... """)
 
 In this example, we've used ConfigParser substitutions for file2 and
@@ -509,13 +502,13 @@
     buildout: Installing data-dir
     data-dir: Creating directory mydata
     buildout: Installing debug
-    File 1 mydata/file
-    File 2 mydata/file.out
-    File 3 var/file3
-    File 4 var/file3/log
-    base var
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
     recipe recipes:debug
 
+Note that the substitution of the data-dir path option reflects the
+update to the option performed by the mkdir recipe.
+
 It might seem surprising that mydata was created again.  This is
 because we changed our recipes package by adding the debug module.
 The buildout system didn't know if this module could effect the mkdir
@@ -526,11 +519,8 @@
     buildout: Develop: /sample-buildout/recipes/setup.py
     buildout: Updating data-dir
     buildout: Updating debug
-    File 1 mydata/file
-    File 2 mydata/file.out
-    File 3 var/file3
-    File 4 var/file3/log
-    base var
+    File 1 /sample-buildout/mydata/file
+    File 2 /sample-buildout/mydata/file/log
     recipe recipes:debug
 
 We can see that mydata was not recreated.
@@ -545,20 +535,12 @@
 Multiple configuration files
 ----------------------------
 
-You can use multiple configuration files.  From your main
-configuration file, you can include other configuration files in 2
-ways:
+A configuration file can "extend" another configuration file.
+Options are read from the other configuration file if they aren't
+already defined by your configuration file.
 
-- Your configuration file can "extend" another configuration file.
-  Option are read from the other configuration file if they aren't
-  already defined by your configuration file.
-
-- Your configuration file can be "extended-by" another configuration
-  file, In this case, the options in the other configuration file
-  override options in your configuration file. 
-
-The configuration files your file extends or is extended by can extend
-or be extended by other configuration files.  The same file may be
+The configuration files your file extends can extend
+other configuration files.  The same file may be
 used more than once although, of course, cycles aren't allowed.
 
 To see how this works, we use an example:
@@ -598,20 +580,16 @@
 
 Here is a more elaborate example. 
 
-    >>> extensions = tmpdir('extensions')
+    >>> other = tmpdir('other')
 
     >>> write(sample_buildout, 'buildout.cfg',
     ... """
     ... [buildout]
-    ... extends = b1.cfg b2.cfg
-    ... extended-by = e1.cfg %(e2)s
+    ... extends = b1.cfg b2.cfg %(b3)s
     ...
     ... [debug]
-    ... op = %%(name)s
-    ...
-    ... [DEFAULT]
-    ... name = buildout
-    ... """ % dict(e2=os.path.join(extensions, 'e2.cfg')))
+    ... op = buildout
+    ... """ % dict(b3=os.path.join(other, 'b3.cfg')))
 
     >>> write(sample_buildout, 'b1.cfg',
     ... """
@@ -619,12 +597,8 @@
     ... extends = base.cfg
     ...
     ... [debug]
-    ... op1 = %(name)s 1
-    ... op2 = %(name)s 2
-    ... op3 = %(name)s 3
-    ...
-    ... [DEFAULT]
-    ... name = b1
+    ... op1 = b1 1
+    ... op2 = b1 2
     ... """)
 
     >>> write(sample_buildout, 'b2.cfg',
@@ -633,91 +607,58 @@
     ... extends = base.cfg
     ...
     ... [debug]
-    ... op3 = %(name)s 3
-    ... op4 = %(name)s 4
-    ... op5 = %(name)s 5
-    ...
-    ... [DEFAULT]
-    ... name = b2
+    ... op2 = b2 2
+    ... op3 = b2 3
     ... """)
 
-    >>> write(sample_buildout, 'base.cfg',
+    >>> write(other, 'b3.cfg',
     ... """
     ... [buildout]
-    ... develop = recipes
-    ... parts = debug
+    ... extends = b3base.cfg
     ...
     ... [debug]
-    ... recipe = recipes:debug
-    ... name = base
+    ... op4 = b3 4
     ... """)
 
-    >>> write(sample_buildout, 'e1.cfg',
+    >>> write(other, 'b3base.cfg',
     ... """
     ... [debug]
-    ... op1 = %(name)s 1
-    ...
-    ... [DEFAULT]
-    ... name = e1
+    ... op5 = b3base 5
     ... """)
 
-    >>> write(extensions, 'e2.cfg',
+    >>> write(sample_buildout, 'base.cfg',
     ... """
     ... [buildout]
-    ... extends = eb.cfg
-    ... extended-by = ee.cfg
-    ... """)
-
-    >>> write(extensions, 'eb.cfg',
-    ... """
-    ... [debug]
-    ... op5 = %(name)s 5
+    ... develop = recipes
+    ... parts = debug
     ...
-    ... [DEFAULT]
-    ... name = eb
-    ... """)
-
-    >>> write(extensions, 'ee.cfg',
-    ... """
     ... [debug]
-    ... op6 = %(name)s 6
-    ...
-    ... [DEFAULT]
-    ... name = ee
+    ... recipe = recipes:debug
+    ... name = base
     ... """)
 
     >>> print system(buildout),
     buildout: Develop: /sample-buildout/recipes/setup.py
     buildout: Uninstalling debug
     buildout: Installing debug
-    name ee
+    name base
     op buildout
-    op1 e1 1
-    op2 b1 2
+    op1 b1 1
+    op2 b2 2
     op3 b2 3
-    op4 b2 4
-    op5 eb 5
-    op6 ee 6
+    op4 b3 4
+    op5 b3base 5
     recipe recipes:debug
 
 There are several things to note about this example:
 
-- We can name multiple files in an extends or extended-by option.
+- We can name multiple files in an extends option.
 
 - We can reference files recursively.
 
-- DEFAULT sections only directly affect the configuration file they're
-  used in, but they can have secondary effects.  For example, the name
-  option showed up in the debug section because it was defined in the
-  debug sections in several of the input files by virtue of being in
-  their DEFAULT sections.
+- Relative file names in extended options are interpreted relative to
+  the directory containing the referencing configuration file.
 
-- Relative file names in extended and extended-by options are
-  interpreted relative to the directory containing the referencing
-  configuration file.  The files eb.cfg and ee.cfg were found in the
-  extensions directory because they were referenced from a file in
-  that directory.
-
 User defaults
 -------------
 
@@ -740,14 +681,13 @@
     buildout: Develop: /sample-buildout/recipes/setup.py
     buildout: Uninstalling debug
     buildout: Installing debug
-    name ee
+    name base
     op buildout
-    op1 e1 1
-    op2 b1 2
+    op1 b1 1
+    op2 b2 2
     op3 b2 3
-    op4 b2 4
-    op5 eb 5
-    op6 ee 6
+    op4 b3 4
+    op5 b3base 5
     op7 7
     recipe recipes:debug
 
@@ -765,16 +705,13 @@
     ... [buildout]
     ... log-level = WARNING
     ... extends = b1.cfg b2.cfg
-    ... extended-by = e1.cfg
     ... """)
 
     >>> print system(buildout),
-    name e1
-    op1 e1 1
-    op2 b1 2
+    name base
+    op1 b1 1
+    op2 b2 2
     op3 b2 3
-    op4 b2 4
-    op5 b2 5
     recipe recipes:debug
 
 Command-line usage
@@ -898,7 +835,6 @@
     d  d2
     d  d3
     d  develop-eggs
-    -  e1.cfg
     d  eggs
     d  parts
     d  recipes
@@ -978,7 +914,6 @@
     d  data3
     d  data4
     d  develop-eggs
-    -  e1.cfg
     d  eggs
     d  parts
     d  recipes
@@ -1055,7 +990,6 @@
     d  data3
     d  data4
     d  develop-eggs
-    -  e1.cfg
     d  eggs
     d  parts
     d  recipes
@@ -1162,12 +1096,11 @@
     ... parts =
     ... log-level = 25
     ... verbosity = 5
-    ... log-format = %%(levelname)s %%(message)s
+    ... log-format = %(levelname)s %(message)s
     ... """)
  
 Here, we've changed the format to include the log-level name, rather
-than the logger name.  Note that we had to double percent signs,
-because configuration options allow ConfigParser variable substitution.
+than the logger name.
 
 We've also illustrated, with a contrived example, that the log level
 can be a numeric value and that the verbosity can be specified in the
@@ -1193,6 +1126,11 @@
     ... """)
 
     >>> print system(buildout+' -v'),
+    zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
+    zc.buildout.easy_install: We have a develop egg for zc.buildout
+    zc.buildout.easy_install: We have the best distribution that satisfies
+    setuptools
+    <BLANKLINE>
     Configuration data:
     [buildout]
     bin-directory = /sample-buildout/bin
@@ -1203,16 +1141,13 @@
     installed = /sample-buildout/.installed.cfg
     log-format = %%(name)s: %%(message)s
     log-level = INFO
+    offline = false
     parts = 
     parts-directory = /sample-buildout/parts
     python = buildout
     verbosity = 10
     <BLANKLINE>
-    zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
-    zc.buildout.easy_install: We have a develop egg for zc.buildout
-    zc.buildout.easy_install: We have the best distribution that satisfies
-    setuptools
-
+ 
 All of these options can be overridden by configuration files or by
 command-line assignments.  We've discussed most of these options
 already, but let's review them and touch on some we haven't discussed:

Modified: zc.buildout/branches/dev/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/branches/dev/src/zc/buildout/tests.py	2006-11-28 14:01:56 UTC (rev 71320)
+++ zc.buildout/branches/dev/src/zc/buildout/tests.py	2006-11-28 22:05:09 UTC (rev 71321)
@@ -71,8 +71,7 @@
     ... ''')
 
     >>> print system(join('bin', 'buildout')+' -v'), # doctest: +ELLIPSIS
-    Configuration data:
-    ...
+    zc.buildout...
     buildout: Develop: /sample-buildout/foo/setup.py
     ...
     Installed /sample-buildout/foo
@@ -86,7 +85,7 @@
 def buildout_error_handling():
     r"""Buildout error handling
 
-Asking for a section that doesn't exist, yields a key error:
+Asking for a section that doesn't exist, yields a missing section error:
 
     >>> import os
     >>> os.chdir(sample_buildout)
@@ -95,7 +94,7 @@
     >>> buildout['eek']
     Traceback (most recent call last):
     ...
-    KeyError: 'eek'
+    MissingSection: The referenced section, 'eek', was not defined.
 
 Asking for an option that doesn't exist, a MissingOption error is raised:
 
@@ -109,8 +108,7 @@
     >>> write(sample_buildout, 'buildout.cfg',
     ... '''
     ... [buildout]
-    ... develop = recipes
-    ... parts = data_dir debug
+    ... parts =
     ... x = ${buildout:y}
     ... y = ${buildout:z}
     ... z = ${buildout:x}
@@ -183,7 +181,7 @@
     ... ''')
 
     >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')),
-    Error: No section was specified for part x
+    Error: The referenced section, 'x', was not defined.
 
 and all parts have to have a specified recipe:
 
@@ -317,20 +315,21 @@
 
     """
 
-def error_for_indefined_install_parts():
-    """
-Any parts we pass to install on the command line must be
-listed in the configuration.
+# Why?
+## def error_for_undefined_install_parts():
+##     """
+## Any parts we pass to install on the command line must be
+## listed in the configuration.
 
-    >>> print system(join('bin', 'buildout') + ' install foo'),
-    buildout: Invalid install parts: foo.
-    Install parts must be listed in the configuration.
+##     >>> print system(join('bin', 'buildout') + ' install foo'),
+##     buildout: Invalid install parts: foo.
+##     Install parts must be listed in the configuration.
 
-    >>> print system(join('bin', 'buildout') + ' install foo bar'),
-    buildout: Invalid install parts: foo bar.
-    Install parts must be listed in the configuration.
+##     >>> print system(join('bin', 'buildout') + ' install foo bar'),
+##     buildout: Invalid install parts: foo bar.
+##     Install parts must be listed in the configuration.
     
-    """
+##     """
 
 
 bootstrap_py = os.path.join(



More information about the Checkins mailing list