[z3-checkins] r14370 - in z3/zopejam/trunk/src: zcmledit zcmledit/tests zopejam

hathawsh at codespeak.net hathawsh at codespeak.net
Thu Jul 7 10:01:21 CEST 2005


Author: hathawsh
Date: Thu Jul  7 10:01:08 2005
New Revision: 14370

Added:
   z3/zopejam/trunk/src/zcmledit/directives.py
   z3/zopejam/trunk/src/zcmledit/inspector.py
   z3/zopejam/trunk/src/zcmledit/interfaces.py
   z3/zopejam/trunk/src/zcmledit/project.py
   z3/zopejam/trunk/src/zcmledit/tests/test_directives.py
   z3/zopejam/trunk/src/zopejam/columns.py
Removed:
   z3/zopejam/trunk/src/zcmledit/include.py
   z3/zopejam/trunk/src/zcmledit/tests/test_include.py
   z3/zopejam/trunk/src/zopejam/inspector.py
Modified:
   z3/zopejam/trunk/src/zcmledit/configfile.py
   z3/zopejam/trunk/src/zcmledit/tests/test_configfile.py
   z3/zopejam/trunk/src/zopejam/main.py
   z3/zopejam/trunk/src/zopejam/notes.txt
   z3/zopejam/trunk/src/zopejam/resources.xrc
Log:
Populate the main directive list.

Refactored quite a bit in the process.  The Project class is now in the
zcmledit package and there is now an element/directive pairing.



Modified: z3/zopejam/trunk/src/zcmledit/configfile.py
==============================================================================
--- z3/zopejam/trunk/src/zcmledit/configfile.py	(original)
+++ z3/zopejam/trunk/src/zcmledit/configfile.py	Thu Jul  7 10:01:08 2005
@@ -14,34 +14,35 @@
 from xml.parsers import expat
 from xml.sax.saxutils import quoteattr
 
-from zcmledit.exceptions import NameResolutionError, NoPackageError
-from zcmledit.include import IncludeResolver, join_package
+from zcmledit import directives
+from zcmledit import interfaces
 
 
 ZCML_CONDITION = (u"http://namespaces.zope.org/zcml", u"condition")
 XMLNS_NS = u"http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
-META_NS = u"http://namespaces.zope.org/meta"
 
 
 class ConfigurationFile:
 
-    def __init__(self, filename, package_name=None):
+    def __init__(self, project, filename, package_name=None):
+        self.project = project
         self.filename = filename
         # package_name is None or the absolute package name that was
         # used for including this config file
         self.package_name = package_name
 
-    def load(self, pythonpath):
-        """Parse the file into a tree of DirectiveElements.
+
+    def load(self):
+        """Parse the file into a tree of Elements.
 
         Stores the result in self.root.  If the file can't be read or
         contains non-well-formed XML, self.error is set and self.root
         is None.
         """
-        self.root = None     # A DirectiveElement
-        self.output = []     # [line, DirectiveElement, line, ...]
-        self.includes = []   # [IncludeResolver]
-        self.definitions = []  # [Definition]
+        self.root = None     # An Element
+        self.output = []     # [line, Element, line, ...]
+        self.includes = []   # [IncludeDirective]
+        self.definitions = []  # [DefinitionDirective]
         self.error = None
 
         fn = self.filename
@@ -66,34 +67,56 @@
             except expat.ExpatError, e:
                 self.error = 'XML parse error: %s' % str(e)
                 return
-            if self.package_name:
-                self.root._setDefaultPackage(self.package_name)
             for e in self.root.flattened():
-                if e.isInclude():
-                    self.includes.append(IncludeResolver(e, pythonpath))
-                elif e.isDefinition():
-                    self.definitions.append(Definition(e))
+                d = self.createDirective(e)
+                e.directive = d
+                if interfaces.IIncludeDirective.providedBy(d):
+                    self.includes.append(d)
+                elif interfaces.IDefinitionDirective.providedBy(d):
+                    self.definitions.append(d)
         finally:
             f.close()
 
+
+    def createDirective(self, element):
+        """Returns an IDirective to fit an XML element"""
+        ns, localname = element.name
+        if localname == 'configure':
+            return directives.ConfigureDirective(element, self.package_name)
+
+        if localname in ('include', 'includeOverrides'):
+            return directives.IncludeDirective(element, self.project)
+
+        if ns == directives.META_NS and localname in (
+            'directive', 'groupingDirective',
+            'complexDirective', 'simpleDirective'):
+            return directives.DefinitionDirective(element)
+
+        return directives.CommonDirective(element)
+
+
     def generateXML(self):
         """Returns a list of XML lines."""
         lines = []
         for item in self.output:
-            if isinstance(item, DirectiveElement):
+            if isinstance(item, Element):
                 lines.extend(item.generateXML())
             else:
                 lines.append(item)
         return lines
 
 
-class DirectiveElement:
+class Element:
     """An XML element that specifies a ZCML directive"""
 
     def __init__(self, config_file, parent, name, data,
                  attr_order, extra_attrs, condition, prefix_uri):
         self.config_file = config_file
         self.parent = parent
+        if parent is not None:
+            self.depth = self.parent.depth + 1
+        else:
+            self.depth = 0
         self.name = tuple(name)  # (ns, localname)
         self.data = data  # {localname: value}
         self.attr_order = attr_order  # list of (ns, localname)
@@ -110,35 +133,41 @@
                 self.uri_prefix[uri] = prefix
 
         # the following are populated by ConfigurationFileHandler
-        self.child_elems = []  # [DirectiveElement]
-        self.output = []       # [line, DirectiveElement, line, ...]
+        self.child_elems = []  # [Element]
+        self.output = []       # [line, Element, line, ...]
 
-        # Collect information from certain XML attributes
-        ns, localname = name
-        if localname == 'configure':
-            # This directive can modify the context package name
-            p = data.get('package')
-            if p:
-                p = p.strip()
-        else:
-            p = None
-        self._package_name = p
+        # self.directive is populated after the file is read
+        self.directive = None
 
-        if localname in ('include', 'includeOverrides'):
-            self._is_include = True
-        else:
-            self._is_include = False
 
-        if ns == META_NS and localname in (
-            'directive', 'groupingDirective',
-            'complexDirective', 'simpleDirective'):
-            self._is_definition = True
-        else:
-            self._is_definition = False
+##        # Collect information from certain XML attributes
+##        self._generic_ns = False
+##        ns, localname = name
+##        if localname == 'configure':
+##            # This directive can modify the context package name
+##            self._generic_ns = True
+##            p = data.get('package')
+##            if p:
+##                p = p.strip()
+##        else:
+##            p = None
+##        self._package_name = p
+
+##        if localname in ('include', 'includeOverrides'):
+##            self._generic_ns = True
+##            self._is_include = True
+##        else:
+##            self._is_include = False
+
+##        if ns == META_NS and localname in (
+##            'directive', 'groupingDirective',
+##            'complexDirective', 'simpleDirective'):
+##            self._is_definition = True
+##        else:
+##            self._is_definition = False
 
-    def _setDefaultPackage(self, package_name):
-        if not self._package_name:
-            self._package_name = package_name
+##    def setPackageName(self, package_name):
+##        self._package_name = package_name
 
     def flattened(self):
         """Returns a list containing this directive and all descendants.
@@ -148,37 +177,41 @@
             res.extend(e.flattened())
         return res
 
-    def getAbsolutePackageName(self):
-        """Returns the absolute package name in context of this directive.
+##    def getAbsolutePackageName(self):
+##        """Returns the absolute package name in context of this directive.
 
-        Used for resolving relative package names.
-        """
-        my_pn = self._package_name
-        if not my_pn:
-            # inherit
-            elem = self.parent
-            while elem is not None:
-                if elem._package_name:
-                    return elem.getAbsolutePackageName()
-                elem = elem.parent
-            raise NoPackageError("No absolute package has been set")
-        elif not my_pn.startswith('.'):
-            return my_pn
-
-        # my_pn is relative, so resolve it.
-        if self.parent is not None:
-            parent_pn = self.parent.getAbsolutePackageName()
-        else:
-            raise NoPackageError("No absolute package has been set")
-        return join_package(parent_pn, my_pn)
-
-    def isInclude(self):
-        """Returns True if this is an include directive"""
-        return self._is_include
-
-    def isDefinition(self):
-        """Returns True if this directive defines a directive type"""
-        return self._is_definition
+##        Used for resolving relative package names.
+##        """
+##        my_pn = self._package_name
+##        if not my_pn:
+##            # inherit
+##            elem = self.parent
+##            while elem is not None:
+##                if elem._package_name:
+##                    return elem.getAbsolutePackageName()
+##                elem = elem.parent
+##            raise NoPackageError("No absolute package has been set")
+##        elif not my_pn.startswith('.'):
+##            return my_pn
+
+##        # my_pn is relative, so resolve it.
+##        if self.parent is not None:
+##            parent_pn = self.parent.getAbsolutePackageName()
+##        else:
+##            raise NoPackageError("No absolute package has been set")
+##        return join_package(parent_pn, my_pn)
+
+##    def isInGenericNamespace(self):
+##        """Returns True if this directive is in the generic (*) namespace"""
+##        return self._generic_ns
+
+##    def isInclude(self):
+##        """Returns True if this is an include directive"""
+##        return self._is_include
+
+##    def isDefinition(self):
+##        """Returns True if this directive defines a directive type"""
+##        return self._is_definition
 
     def getXMLName(self, ns, localname):
         """Returns the tag or attribute for a given namespace and localname"""
@@ -271,7 +304,7 @@
         lines = self.generateStartTag(indent=indent, end=False)
         indent2 = indent + u'  '
         for item in self.output:
-            if isinstance(item, DirectiveElement):
+            if isinstance(item, Element):
                 lines.extend(item.generateXML(indent2))
             else:
                 lines.append((indent2 + item).rstrip())
@@ -282,14 +315,14 @@
 
 
 class ConfigurationFileHandler:
-    """Generates a DirectiveElement tree from Expat parser events"""
+    """Generates an Element tree from Expat parser events"""
 
     def __init__(self, cf):
         self.cf = cf
         self.root = None
-        self.dir_stack = []      # [DirectiveElement]
+        self.dir_stack = []      # [Element]
         self.ns_stack = [{}]     # [{prefix: namespace}]
-        self.output_stack = [[]]  # [[text, DirectiveElement, text, ...]]
+        self.output_stack = [[]]  # [[text, Element, text, ...]]
 
     def parse(self, f):
         p = expat.ParserCreate(None, ' ')
@@ -352,7 +385,7 @@
             parent = self.dir_stack[-1]
         else:
             parent = None
-        d = DirectiveElement(
+        d = Element(
             self.cf, parent, name, data, attr_order, extra_attrs,
             condition, pm)
         self.dir_stack.append(d)
@@ -361,10 +394,10 @@
         self.ns_stack.append({})
 
         if parent is not None:
-            # add to a DirectiveElement
+            # add to an Element
             parent.child_elems.append(d)
         else:
-            # create the root DirectiveElement
+            # create the root Element
             self.root = d
 
     def EndElementHandler(self, name):
@@ -389,7 +422,7 @@
     buf = []
     first_line = file_scope
     for obj in items + [None]:
-        is_directive = isinstance(obj, DirectiveElement)
+        is_directive = isinstance(obj, Element)
         if is_directive or obj is None:
             # Gather the accumulated text and replace tabs.  If there
             # are non-blank lines or extra blank lines, add a list of
@@ -429,80 +462,3 @@
         return lines
     return [line[count:] for line in lines]
 
-
-class Definition:
-    """A directive definition"""
-
-    def __init__(self, element):
-        self.element = element
-        self.update()
-
-    def update(self):
-        self.error = None
-        self.name = self.element.data.get('name')
-        self.schema = self.element.data.get('schema')
-
-        ns = None
-        parent = self.element.parent
-        while parent is not None:
-            if parent.name == (META_NS, 'directives'):
-                # use the namespace inherited from a 'directives' directive
-                ns = parent.data['namespace']
-                break
-            parent = parent.parent
-        if ns is None:
-            ns = self.element.data.get('namespace')
-
-        self.fullname = (ns, self.name)
-        if self.schema:
-            if self.schema.startswith('.'):
-                try:
-                    pkg = self.element.getAbsolutePackageName()
-                    abs_schema = join_package(pkg, self.schema)
-                except NameResolutionError, e:
-                    abs_schema = None
-                    self.error = '%s: %s' % (e.__class__.__name__, str(e))
-            else:
-                abs_schema = self.schema
-        else:
-            self.error = "Missing 'schema' attribute"
-        self.abs_schema = abs_schema
-
-
-class BootstrapDefinition:
-    """A bootstrap directive definition.
-
-    Not defined in any configuration file.
-    """
-
-    def __init__(self, ns, name, abs_schema):
-        self.element = None
-        self.error = None
-        self.name = name
-        self.fullname = (ns, name)
-        self.schema = abs_schema
-        self.abs_schema = abs_schema
-
-
-def createBootstrapDefinitions():
-    """Returns a dictionary containing the bootstrap directive definitions"""
-    res = {}
-    for ns, name, schema in [
-        ('*', 'configure', "zope.configuration.zopeconfigure.IZopeConfigure"),
-        ('*', 'include', "zope.configuration.xmlconfig.IInclude"),
-        ('*', 'includeOverride', "zope.configuration.xmlconfig.IInclude"),
-        (META_NS, 'directive',
-         "zope.configuration.config.IFullInfo"),
-        (META_NS, 'groupingDirective',
-         "zope.configuration.config.IFullInfo"),
-        (META_NS, 'directives',
-         "zope.configuration.config.IDirectivesInfo"),
-        (META_NS, 'complexDirective',
-         "zope.configuration.config.IFullInfo"),
-        (META_NS, 'subdirective',
-         "zope.configuration.config.IDirectiveInfo"),
-        (META_NS, 'provides',
-         "zope.configuration.config.IProvidesDirectiveInfo"),
-        ]:
-        res[(ns, name)] = BootstrapDefinition(ns, name, schema)
-    return res

Added: z3/zopejam/trunk/src/zcmledit/directives.py
==============================================================================
--- (empty file)
+++ z3/zopejam/trunk/src/zcmledit/directives.py	Thu Jul  7 10:01:08 2005
@@ -0,0 +1,298 @@
+
+from glob import glob
+import os
+
+from zope.interface import implements
+
+from zcmledit import interfaces
+from zcmledit.exceptions import NameResolutionError, NoPackageError
+from zcmledit.exceptions import InvalidRelativeNameError, MissingPackageError
+
+META_NS = u"http://namespaces.zope.org/meta"
+
+
+class Directive(object):
+
+    def __init__(self, element):
+        self.element = element
+        self.abs_package = None
+        self.errors = []
+
+    def getInheritedPackageName(self):
+        ancestor = self.element.parent
+        while ancestor is not None:
+            res = ancestor.directive.abs_package
+            if res is not None:
+                return res
+            ancestor = ancestor.parent
+        return None
+
+    def prepareAbsolutePackage(self, package_attr=None):
+        """Determine the absolute package name, which may be None.
+
+        Stores in the abs_package attribute.
+        """
+        if not package_attr:
+            self.abs_package = self.getInheritedPackageName()
+        elif package_attr.startswith('.'):
+            # Turn the relative package name into an absolute package
+            # name.  In this case, the directive did specify a
+            # package, so complain if there is no absolute package
+            # name in this context.
+            parent = self.getInheritedPackageName()
+            try:
+                self.abs_package = join_package(parent, package_attr)
+            except NameResolutionError, e:
+                self.abs_package = None
+                self.errors.append('%s: %s' % (e.__class__.__name__, str(e)))
+        else:
+            self.abs_package = package_attr
+
+    def getAbsolutePackageName(self):
+        if self.abs_package is not None:
+            return self.abs_package
+        return self.getInheritedPackageName()
+
+
+class ConfigureDirective(Directive):
+    implements(interfaces.IConfigureDirective)
+
+    def __init__(self, element, config_file_package=None):
+        Directive.__init__(self, element)
+        self.config_file_package = config_file_package
+        self.update()
+
+    def update(self):
+        self.errors = []
+        self.package = self.element.data.get('package')
+        self.prepareAbsolutePackage(self.package)
+
+    def getInheritedPackageName(self):
+        if self.element.parent is None:
+            return self.config_file_package
+        else:
+            return Directive.getInheritedPackageName(self)
+
+
+class IncludeDirective(Directive):
+    """Resolves an include directive to a set of files.
+
+    Does not load the configured Python code to find packages.
+    """
+    implements(interfaces.IIncludeDirective)
+
+    def __init__(self, element, project):
+        Directive.__init__(self, element)
+        self.project = project
+        self.update()
+
+    def update(self):
+        """Read the directive element and prepare the filename pattern.
+
+        The constructor calls this method, but it can be called again
+        when the directive element changes, a Python package is added,
+        or the pythonpath changes.
+        """
+        element = self.element
+        ns, localname = element.name
+        self.override = (localname == 'includeOverrides')
+        self.files = element.data.get('files')
+        self.file = element.data.get('file')
+        self.package = element.data.get('package')
+        self.glob = bool(self.files)
+        self.errors = []
+
+        # If there is bad data in the directive, add to self.errors
+        # rather than raise an exception so that the error can be
+        # presented in a helpful way at a later time.
+
+        if self.files and self.file:
+            self.errors.append("Must specify only one of file or files")
+        if not self.files and not self.file:
+            self.file = 'configure.zcml'
+        self.prepareAbsolutePackage(self.package)
+
+        # Determine the filename pattern
+        pattern = os.path.normpath(self.files or self.file)
+        if not os.path.isabs(pattern):
+            # convert to an absolute path
+            if self.abs_package:
+                try:
+                    basepath = find_package(
+                        self.abs_package, self.project.pythonpath)
+                except NameResolutionError, e:
+                    basepath = None
+                    self.errors.append(
+                        '%s: %s' % (e.__class__.__name__, str(e)))
+            else:
+                cfn = element.config_file.filename
+                basepath = os.path.abspath(os.path.dirname(cfn))
+            if basepath is not None:
+                pattern = os.path.join(basepath, pattern)
+            else:
+                pattern = None
+
+        # set self.filenames, the list of files to include
+        if self.errors:
+            self.filenames = []
+        elif self.glob:
+            self.filenames = glob(pattern)
+        else:
+            self.filenames = [pattern]
+
+    def listFiles(self):
+        """Returns the list of files to include.
+
+        May return names of files that do not exist.
+        """
+        return self.filenames
+
+
+class DefinitionDirective(Directive):
+    """A directive that defines other directives"""
+    implements(interfaces.IDefinitionDirective)
+
+    def __init__(self, element):
+        Directive.__init__(self, element)
+        self.update()
+
+    def update(self):
+        self.errors = []
+        self.name = self.element.data.get('name')
+        self.schema = self.element.data.get('schema')
+        self.prepareAbsolutePackage()
+
+        ns = None
+        parent = self.element.parent
+        while parent is not None:
+            if parent.name == (META_NS, 'directives'):
+                # use the namespace inherited from a 'directives' directive
+                ns = parent.data['namespace']
+                break
+            parent = parent.parent
+        if ns is None:
+            ns = self.element.data.get('namespace')
+
+        self.fullname = (ns, self.name)
+        if self.schema:
+            if self.schema.startswith('.'):
+                try:
+                    abs_schema = join_package(self.abs_package, self.schema)
+                except NameResolutionError, e:
+                    abs_schema = None
+                    self.errors.append(
+                        '%s: %s' % (e.__class__.__name__, str(e)))
+            else:
+                abs_schema = self.schema
+        else:
+            self.errors.append("Missing 'schema' attribute")
+        self.abs_schema = abs_schema
+
+
+class CommonDirective(Directive):
+    implements(interfaces.IDirective)
+
+
+def join_package(parent, child):
+    """Joins an absolute package name with a relative object name."""
+    if child == '.':
+        return parent
+    if child.endswith('.'):
+        raise NameResolutionError("Object names must not end with '.'")
+    if not child.startswith('.'):
+        return child
+    if not parent:
+        raise NoPackageError("No absolute package has been set")
+    pnames = parent.split('.') + ['']
+    cnames = child.split('.')
+    while cnames and not cnames[0]:
+        cnames.pop(0)
+        try:
+            pnames.pop()
+        except IndexError:
+            raise InvalidRelativeNameError(
+                "Can't join absolute package '%s' with "
+                "relative name '%s'" % (parent, child))
+    return '.'.join(pnames + cnames)
+
+
+def find_package(package, pythonpath):
+    """Returns the directory containing the specified Python package name."""
+    parts = package.split('.')
+    check_dirs = []
+    for i in range(len(parts), 0, -1):
+        check_dirs.append(os.path.join(*parts[:i]))
+    subpath = check_dirs[0]
+
+    candidate = None
+    for base in pythonpath:
+        res = os.path.join(base, subpath)
+        if not os.path.isdir(res):
+            # No such directory.  Keep searching.
+            continue
+        # Found a candidate directory.  Verify there are __init__ files.
+        candidate = res
+        valid = True
+        for dir in check_dirs:
+            fn = os.path.join(base, dir, '__init__.py')
+            for suffix in ('', 'c', 'o'):
+                if os.path.isfile(fn + suffix):
+                    break
+            else:
+                # No __init__ found, so this isn't really a Python package.
+                valid = False
+                break
+        if valid:
+            # Found it.
+            return res
+
+    msg = "Can't find a Python package named '%s'." % package
+    if candidate:
+        # Remind the user to add __init__.py files
+        msg += ("  (Directory '%s' is a candidate, but one "
+                "or more '__init__.py' files are required.)" % candidate)
+    raise MissingPackageError(msg)
+
+
+class BootstrapDirective(Directive):
+    """A bootstrap definition directive.
+
+    Not defined in any configuration file.
+    """
+    implements(interfaces.IDefinitionDirective)
+
+    def __init__(self, ns, name, abs_schema):
+        self.element = None
+        self.errors = ()
+        self.abs_package = None
+
+        self.name = name
+        self.fullname = (ns, name)
+        self.schema = abs_schema
+        self.abs_schema = abs_schema
+
+
+def createBootstrapDirectives():
+    """Returns a dictionary containing the bootstrap directive definitions"""
+    res = {}
+    for ns, name, schema in [
+        ('*', 'configure', "zope.configuration.zopeconfigure.IZopeConfigure"),
+        ('*', 'include', "zope.configuration.xmlconfig.IInclude"),
+        ('*', 'includeOverride', "zope.configuration.xmlconfig.IInclude"),
+        (META_NS, 'directive',
+         "zope.configuration.config.IFullInfo"),
+        (META_NS, 'groupingDirective',
+         "zope.configuration.config.IFullInfo"),
+        (META_NS, 'directives',
+         "zope.configuration.config.IDirectivesInfo"),
+        (META_NS, 'complexDirective',
+         "zope.configuration.config.IFullInfo"),
+        (META_NS, 'subdirective',
+         "zope.configuration.config.IDirectiveInfo"),
+        (META_NS, 'provides',
+         "zope.configuration.config.IProvidesDirectiveInfo"),
+        ]:
+        res[(ns, name)] = BootstrapDirective(ns, name, schema)
+    return res
+
+

Deleted: /z3/zopejam/trunk/src/zcmledit/include.py
==============================================================================
--- /z3/zopejam/trunk/src/zcmledit/include.py	Thu Jul  7 10:01:08 2005
+++ (empty file)
@@ -1,174 +0,0 @@
-
-# (c) 2005 Shane Hathaway
-# License: ZPL 2.1
-
-"""Code for resolving include directives in ZCML"""
-
-from glob import glob
-import os
-
-from zcmledit.exceptions import NameResolutionError, NoPackageError
-from zcmledit.exceptions import InvalidRelativeNameError
-from zcmledit.exceptions import MissingPackageError
-
-
-class IncludeResolver:
-    """Resolves an include directive to a set of files.
-
-    Does not load the configured Python code to find packages.
-    """
-
-    def __init__(self, element, pythonpath):
-        self.element = element
-        self.resolve(pythonpath)
-
-    def resolve(self, pythonpath):
-        """Read the directive element and prepare the filename pattern.
-
-        The constructor calls this method, but it can be called again
-        when the directive element changes, a Python package is added,
-        or the pythonpath changes.
-        """
-        element = self.element
-        ns, localname = element.name
-        self.override = (localname == 'includeOverrides')
-        self.files = element.data.get('files')
-        self.file = element.data.get('file')
-        self.package = element.data.get('package')
-        self.glob = bool(self.files)
-        self.error = None
-
-        # If there is bad data in the directive, set self.error rather
-        # than raise an exception so that the error can be presented
-        # in a helpful way at a later time.
-
-        if self.files and self.file:
-            self.error = "Must specify only one of file or files"
-        if not self.files and not self.file:
-            self.file = 'configure.zcml'
-
-        # Determine the absolute package name, which may be None.
-        try:
-            if not self.package:
-                try:
-                    abs_package = element.getAbsolutePackageName()
-                except NoPackageError:
-                    # Not in a package, but that's okay because the
-                    # directive didn't ask for a package. The directive is
-                    # referring to a file relative to the file containing
-                    # the include directive.
-                    abs_package = None
-            elif self.package.startswith('.'):
-                # Turn the relative package name into an absolute package
-                # name.  In this case, the directive did specify a
-                # package, so complain if there is no absolute package
-                # name in this context.
-                parent = element.getAbsolutePackageName()
-                abs_package = join_package(parent, self.package)
-            else:
-                abs_package = self.package
-        except NameResolutionError, e:
-            abs_package = None
-            self.error = '%s: %s' % (e.__class__.__name__, str(e))
-        self.abs_package = abs_package
-
-        # Determine the filename pattern
-        pattern = os.path.normpath(self.files or self.file)
-        if not os.path.isabs(pattern):
-            # convert to an absolute path
-            if abs_package:
-                try:
-                    basepath = find_package(abs_package, pythonpath)
-                except NameResolutionError, e:
-                    basepath = None
-                    self.error = '%s: %s' % (e.__class__.__name__, str(e))
-            else:
-                cfn = element.config_file.filename
-                basepath = os.path.abspath(os.path.dirname(cfn))
-            if basepath is not None:
-                pattern = os.path.join(basepath, pattern)
-            else:
-                pattern = None
-
-        # set self.filenames, the list of files to include
-        if self.error:
-            self.filenames = []
-        elif self.glob:
-            self.filenames = glob(pattern)
-        else:
-            self.filenames = [pattern]
-
-    def getAbsolutePackageName(self):
-        """Returns the package name that included files belong to.
-
-        Returns None if the files are not in a package.
-        """
-        return self.abs_package
-
-    def listFiles(self):
-        """Returns the list of files to include.
-
-        May return names of files that do not exist.
-        """
-        return self.filenames
-
-
-def join_package(parent, child):
-    """Joins an absolute package name with a relative object name."""
-    if child == '.':
-        return parent
-    if child.endswith('.'):
-        raise NameResolutionError("Object names must not end with '.'")
-    if not child.startswith('.'):
-        return child
-    if not parent:
-        raise NoPackageError("No absolute package has been set")
-    pnames = parent.split('.') + ['']
-    cnames = child.split('.')
-    while cnames and not cnames[0]:
-        cnames.pop(0)
-        try:
-            pnames.pop()
-        except IndexError:
-            raise InvalidRelativeNameError(
-                "Can't join absolute package '%s' with "
-                "relative name '%s'" % (parent, child))
-    return '.'.join(pnames + cnames)
-
-
-def find_package(package, pythonpath):
-    """Returns the directory containing the specified Python package name."""
-    parts = package.split('.')
-    check_dirs = []
-    for i in range(len(parts), 0, -1):
-        check_dirs.append(os.path.join(*parts[:i]))
-    subpath = check_dirs[0]
-
-    candidate = None
-    for base in pythonpath:
-        res = os.path.join(base, subpath)
-        if not os.path.isdir(res):
-            # No such directory.  Keep searching.
-            continue
-        # Found a candidate directory.  Verify there are __init__ files.
-        candidate = res
-        valid = True
-        for dir in check_dirs:
-            fn = os.path.join(base, dir, '__init__.py')
-            for suffix in ('', 'c', 'o'):
-                if os.path.isfile(fn + suffix):
-                    break
-            else:
-                # No __init__ found, so this isn't really a Python package.
-                valid = False
-                break
-        if valid:
-            # Found it.
-            return res
-
-    msg = "Can't find a Python package named '%s'." % package
-    if candidate:
-        # Remind the user to add __init__.py files
-        msg += ("  (Directory '%s' is a candidate, but one "
-                "or more '__init__.py' files are required.)" % candidate)
-    raise MissingPackageError(msg)

Added: z3/zopejam/trunk/src/zcmledit/inspector.py
==============================================================================
--- (empty file)
+++ z3/zopejam/trunk/src/zcmledit/inspector.py	Thu Jul  7 10:01:08 2005
@@ -0,0 +1,78 @@
+
+# (c) 2005 Shane Hathaway
+# License: ZPL 2.1
+
+"""This standalone module inspects configured code.
+
+Zope Jam uses this module to provide a degree of isolation between the
+configuration UI and code under development.  Also, this strategy
+makes it possible to re-inspect some of the code without restarting.
+
+"""
+
+import cPickle
+import os
+import sys
+
+from zope.interface.interfaces import IInterface
+
+_import_chickens = {}, {}, ("*",) # dead chickens needed by __import__
+
+
+def inspect_interfaces(names):
+    """Returns {name: {'doc', 'bases', 'attrs' | 'error'}}"""
+    res = {}
+    # the names are absolute.
+    todo = list(names)
+    while todo:
+        name = todo.pop()
+        if res.has_key(name):
+            continue
+        info = {}
+        res[name] = info
+
+        pos = name.rfind('.')
+        if pos >= 0:
+            mname = name[:pos]
+            oname = name[pos + 1:]
+        else:
+            mname = ''
+            oname = name
+
+        try:
+            module = __import__(mname, *_import_chickens)
+            obj = getattr(module, oname)
+        except:
+            info['error'] = str(sys.exc_info()[1])
+            continue
+
+        if IInterface.providedBy(obj):
+            info['doc'] = getattr(obj, '__doc__', None)
+            bases = []
+            for base in getattr(obj, '__bases__', ()):
+                m = getattr(base, '__module__', None)
+                n = getattr(base, '__name__', None)
+                if m and n:
+                    fullname = '%s.%s' % (m, n)
+                    bases.append(fullname)
+                    if not res.has_key(fullname):
+                        todo.append(fullname)
+            info['bases'] = bases
+            attrs = {}
+            for n in obj.names(False):
+                attrs[n] = obj[n]
+            info['attributes'] = attrs
+        else:
+            info['error'] = 'not an interface; type is %s' % str(type(obj))
+    return res
+
+
+def main():
+    # pickle in, pickle out.
+    funcname, args, kwargs = cPickle.load(sys.stdin)
+    func = globals()[funcname]
+    res = func(*args, **kwargs)
+    cPickle.dump(res, sys.stdout, protocol=2)
+
+if __name__ == '__main__':
+    main()

Added: z3/zopejam/trunk/src/zcmledit/interfaces.py
==============================================================================
--- (empty file)
+++ z3/zopejam/trunk/src/zcmledit/interfaces.py	Thu Jul  7 10:01:08 2005
@@ -0,0 +1,21 @@
+
+
+from zope.interface import Interface
+
+class IDirective(Interface):
+    pass
+
+class IGenericNamespaceDirective(IDirective):
+    """A directive that can be specified in any namespace"""
+
+class IConfigureDirective(IGenericNamespaceDirective):
+    pass
+
+class IIncludeDirective(IGenericNamespaceDirective):
+    pass
+
+class IDefinitionDirective(IDirective):
+    pass
+
+class IProject(Interface):
+    pass

Added: z3/zopejam/trunk/src/zcmledit/project.py
==============================================================================
--- (empty file)
+++ z3/zopejam/trunk/src/zcmledit/project.py	Thu Jul  7 10:01:08 2005
@@ -0,0 +1,116 @@
+
+# (c) 2005 Shane Hathaway
+# License: ZPL 2.1
+
+"""A set of connected configuration files"""
+
+
+import cPickle
+import os
+import subprocess
+import sys
+
+from zope.interface import implements
+
+from zcmledit import interfaces
+from zcmledit.configfile import ConfigurationFile
+from zcmledit import directives
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+class Project:
+    implements(interfaces.IProject)
+
+    def __init__(self, root_filename, interpreter=None, pythonpath=None):
+        self.root_filename = root_filename  # '/path/to/site.zcml'
+        self.base = os.path.dirname(root_filename)
+        if interpreter is None:
+            interpreter = sys.executable
+        self.interpreter = interpreter
+        if pythonpath is None:
+            pythonpath = [os.path.join(self.base, 'src')]
+        self.pythonpath = pythonpath
+
+    def load(self):
+        self.packages = {}  # {package name: [filename]}
+        self.files = {}     # {filename: config_file}
+        # self.definitions contains {(ns, localname): IDirectiveDefinition}
+        self.definitions = directives.createBootstrapDirectives()
+        self.root_config = ConfigurationFile(self, self.root_filename)
+        self.files[self.root_filename] = self.root_config
+
+        todo = [self.root_config]
+        while todo:
+            cfg = todo.pop(0)
+            cfg.load()
+
+            # add to self.packages
+            filenames = self.packages.get(cfg.package_name)
+            if filenames is None:
+                filenames = []
+                self.packages[cfg.package_name] = filenames
+            filenames.append(cfg.filename)
+
+            # add to self.files
+            for inc in cfg.includes:
+                for fn in inc.listFiles():
+                    if not self.files.has_key(fn):
+                        subcfg = ConfigurationFile(
+                            self, fn, inc.getAbsolutePackageName())
+                        self.files[fn] = subcfg
+                        todo.append(subcfg)
+
+            # add to self.definitions
+            for dfn in cfg.definitions:
+                if self.definitions.has_key(dfn.fullname):
+                    raise RuntimeError("can't handle multiple definitions for "
+                                       "directive %s" % repr(dfn.fullname))
+                self.definitions[dfn.fullname] = dfn
+
+        # load schemas
+        names = [dfn.abs_schema for dfn in self.definitions.values()]
+        self.schemas = self.call_inspector('inspect_interfaces', names)
+
+    def call_inspector(self, funcname, *args, **kw):
+        inspector_py = os.path.join(here, 'inspector.py')
+        env = os.environ.copy()
+        env['PYTHONPATH'] = os.pathsep.join(self.pythonpath)
+        p = subprocess.Popen(
+            [self.interpreter, inspector_py],
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            env=env)
+        cPickle.dump((funcname, args, kw), p.stdin, protocol=2)
+        res_pickle = p.stdout.read()
+        exit_code = p.wait()
+        if exit_code:
+            raise RuntimeError(
+                "inspector failed with exit code %s" % exit_code)
+        res = cPickle.loads(res_pickle)
+        return res
+
+    def print_fields(self):
+        """Shows reuse of fields by different directive types."""
+        fields = {}  # {(field_name, field_type) -> [schema_name]}
+        for schema_name, info in self.schemas.items():
+            if not info.has_key('attributes'):
+                continue
+            for attr_name, obj in info['attributes'].items():
+                if not IField.providedBy(obj):
+                    continue
+                key = (attr_name, type(obj))
+                lst = fields.get(key)
+                if lst is None:
+                    lst = []
+                    fields[key] = lst
+                lst.append(schema_name)
+        import pprint
+        pprint.pprint(fields)
+
+    def shortenPath(self, p):
+        if p.startswith(self.base):
+            len_base = len(self.base)
+            if len(p) > len_base and p[len_base] == os.sep:
+                return p[len_base + 1:]
+        return p

Modified: z3/zopejam/trunk/src/zcmledit/tests/test_configfile.py
==============================================================================
--- z3/zopejam/trunk/src/zcmledit/tests/test_configfile.py	(original)
+++ z3/zopejam/trunk/src/zcmledit/tests/test_configfile.py	Thu Jul  7 10:01:08 2005
@@ -7,6 +7,7 @@
 import sys
 
 import zcmledit
+from zcmledit.project import Project
 from zcmledit.configfile import ConfigurationFile
 
 tests_dir = os.path.join(os.path.dirname(zcmledit.__file__), 'tests')
@@ -14,8 +15,9 @@
 
 def load_root():
     fn = os.path.join(tests_dir, 'root.zcml')
-    cf = ConfigurationFile(fn)
-    cf.load(sys.path)
+    proj = Project(fn, pythonpath=sys.path)
+    cf = ConfigurationFile(proj, fn)
+    cf.load()
     return cf
 
 def test_load_root():
@@ -34,8 +36,9 @@
             yield try_one_preservation, fn
 
 def try_one_preservation(fn):
-    cf = ConfigurationFile(fn)
-    cf.load(sys.path)
+    proj = Project(fn, pythonpath=sys.path)
+    cf = ConfigurationFile(proj, fn)
+    cf.load()
     f = open(fn, 'rb')
     file_contents = [line.rstrip() for line in f.readlines()]
     f.close()

Added: z3/zopejam/trunk/src/zcmledit/tests/test_directives.py
==============================================================================
--- (empty file)
+++ z3/zopejam/trunk/src/zcmledit/tests/test_directives.py	Thu Jul  7 10:01:08 2005
@@ -0,0 +1,85 @@
+
+# (c) 2005 Shane Hathaway
+# License: ZPL 2.1
+
+import os
+import py.test
+import sys
+
+import zcmledit
+from zcmledit.configfile import ConfigurationFile
+from zcmledit.directives import join_package, find_package
+from zcmledit.exceptions import NameResolutionError, NoPackageError
+from zcmledit.exceptions import InvalidRelativeNameError
+from zcmledit.exceptions import MissingPackageError
+from zcmledit.project import Project
+
+tests_dir = os.path.join(os.path.dirname(zcmledit.__file__), 'tests')
+
+
+def test_join_package():
+    assert join_package('zope', '.app') == 'zope.app'
+    assert join_package('zope', '..os') == 'os'
+    assert join_package('zope.app', '.content') == 'zope.app.content'
+    assert join_package('zope.app', '..configuration') == 'zope.configuration'
+    assert join_package('zope.app', '...os') == 'os'
+    assert join_package('zope.app', '...os.path') == 'os.path'
+    assert join_package('zope.app', 'os.path') == 'os.path'
+    assert join_package('zope.app', '.') == 'zope.app'
+    assert join_package('', 'zope') == 'zope'
+
+    py.test.raises(NoPackageError, join_package, '', '.foo')
+    py.test.raises(NameResolutionError, join_package, 'zope', '.foo.')
+    py.test.raises(InvalidRelativeNameError, join_package, 'zope', '...os')
+
+
+def test_find_package():
+    base = os.path.dirname(os.path.dirname(zcmledit.__file__))
+
+    assert find_package('zcmledit', sys.path) == os.path.join(base, 'zcmledit')
+    assert find_package('zcmledit.tests', sys.path) == os.path.join(
+        base, 'zcmledit', 'tests')
+    py.test.raises(
+        MissingPackageError, find_package, 'zcmledit.nonexistentpackage',
+        sys.path)
+    # zcmledit.configfile is a module, not a package
+    py.test.raises(
+        MissingPackageError, find_package, 'zcmledit.configfile', sys.path)
+
+    try:
+        find_package('zcmledit.tests.preserve', sys.path)
+    except MissingPackageError, e:
+        # The error message should suggest that __init__.py be added
+        # to the tests/preserve directory.  (Which shouldn't actually
+        # be done, or this test will break. ;-) )
+        expect = os.path.join(base, 'zcmledit', 'tests', 'preserve')
+        assert expect in e.args[0]
+
+
+def test_load_project():
+    root_fn = os.path.join(tests_dir, 'root.zcml')
+    proj = Project(root_fn, pythonpath=sys.path)
+    proj.load()
+    
+    # Verify the code read all the right files, in the right order
+    actual = []
+    for fn in proj.files.keys():
+        assert fn.startswith(tests_dir)
+        parts = fn[len(tests_dir):].split(os.sep)
+        if not parts[0]:
+            parts = parts[1:]
+        actual.append(tuple(parts))
+    actual.sort()
+
+    expected = [
+        ('include', 'test1-configure.zcml'),
+        ('include', 'test1-meta.zcml'),
+        ('package', 'conf1.zcml'),
+        ('package', 'conf2.zcml'),
+        ('package', 'conf3.zcml'),
+        ('package', 'configure.zcml'),
+        ('package', 'meta.zcml'),
+        ('package', 'overrides.zcml'),
+        ('root.zcml',),
+        ]
+    assert actual == expected

Deleted: /z3/zopejam/trunk/src/zcmledit/tests/test_include.py
==============================================================================
--- /z3/zopejam/trunk/src/zcmledit/tests/test_include.py	Thu Jul  7 10:01:08 2005
+++ (empty file)
@@ -1,95 +0,0 @@
-
-# (c) 2005 Shane Hathaway
-# License: ZPL 2.1
-
-import os
-import py.test
-import sys
-
-import zcmledit
-from zcmledit.configfile import ConfigurationFile
-from zcmledit.include import join_package, find_package
-from zcmledit.exceptions import NameResolutionError, NoPackageError
-from zcmledit.exceptions import InvalidRelativeNameError
-from zcmledit.exceptions import MissingPackageError
-
-tests_dir = os.path.join(os.path.dirname(zcmledit.__file__), 'tests')
-
-
-def test_join_package():
-    assert join_package('zope', '.app') == 'zope.app'
-    assert join_package('zope', '..os') == 'os'
-    assert join_package('zope.app', '.content') == 'zope.app.content'
-    assert join_package('zope.app', '..configuration') == 'zope.configuration'
-    assert join_package('zope.app', '...os') == 'os'
-    assert join_package('zope.app', '...os.path') == 'os.path'
-    assert join_package('zope.app', 'os.path') == 'os.path'
-    assert join_package('zope.app', '.') == 'zope.app'
-    assert join_package('', 'zope') == 'zope'
-
-    py.test.raises(NoPackageError, join_package, '', '.foo')
-    py.test.raises(NameResolutionError, join_package, 'zope', '.foo.')
-    py.test.raises(InvalidRelativeNameError, join_package, 'zope', '...os')
-
-
-def test_find_package():
-    base = os.path.dirname(os.path.dirname(zcmledit.__file__))
-
-    assert find_package('zcmledit', sys.path) == os.path.join(base, 'zcmledit')
-    assert find_package('zcmledit.tests', sys.path) == os.path.join(
-        base, 'zcmledit', 'tests')
-    py.test.raises(
-        MissingPackageError, find_package, 'zcmledit.nonexistentpackage',
-        sys.path)
-    # zcmledit.configfile is a module, not a package
-    py.test.raises(
-        MissingPackageError, find_package, 'zcmledit.configfile', sys.path)
-
-    try:
-        find_package('zcmledit.tests.preserve', sys.path)
-    except MissingPackageError, e:
-        # The error message should suggest that __init__.py be added
-        # to the tests/preserve directory.  (Which shouldn't actually
-        # be done, or this test will break. ;-) )
-        expect = os.path.join(base, 'zcmledit', 'tests', 'preserve')
-        assert expect in e.args[0]
-
-
-def load_and_resolve(fn, package_name, fn_list):
-    """Recursively read configuration files with include directives"""
-    fn_list.append(fn)
-    cf = ConfigurationFile(fn, package_name)
-    cf.load(sys.path)
-    for resolver in cf.includes:
-        subpackage = resolver.getAbsolutePackageName()
-        for include_fn in resolver.listFiles():
-            assert include_fn not in fn_list
-            load_and_resolve(include_fn, subpackage, fn_list)
-
-
-def test_resolve_includes():
-    all_files = []
-    root_fn = os.path.join(tests_dir, 'root.zcml')
-    load_and_resolve(root_fn, None, all_files)
-
-    # Verify the code read all the right files, in the right order
-    actual = []
-    for fn in all_files:
-        assert fn.startswith(tests_dir)
-        parts = fn[len(tests_dir):].split(os.sep)
-        if not parts[0]:
-            parts = parts[1:]
-        actual.append(tuple(parts))
-
-    expected = [
-        ('root.zcml',),
-        ('include', 'test1-meta.zcml'),
-        ('package', 'meta.zcml'),
-        ('include', 'test1-configure.zcml'),
-        ('package', 'configure.zcml'),
-        ('package', 'conf1.zcml'),
-        ('package', 'conf2.zcml'),
-        ('package', 'conf3.zcml'),
-        ('package', 'overrides.zcml')
-        ]
-    assert actual == expected

Added: z3/zopejam/trunk/src/zopejam/columns.py
==============================================================================
--- (empty file)
+++ z3/zopejam/trunk/src/zopejam/columns.py	Thu Jul  7 10:01:08 2005
@@ -0,0 +1,158 @@
+
+# (c) 2005 Shane Hathaway
+# License: ZPL 2.1
+
+"""Column types for the directive list"""
+
+import os
+
+from zcmledit.directives import join_package
+from zcmledit.exceptions import NameResolutionError
+from zcmledit import interfaces
+
+nzo_prefix = 'http://namespaces.zope.org/'
+
+
+class Column:
+    label = ''
+    default_width = 100
+
+    def setProject(self, project):
+        self.project = project
+
+    def getText(self, d):
+        return ''
+
+    def resolve(self, d, name):
+        """Tries to resolve a package name,
+
+        Returns (name, error)
+        """
+        if name.startswith('.'):
+            try:
+                parent = d.getAbsolutePackageName()
+                text = join_package(parent, name)
+                return text, None
+            except NameResolutionError, e:
+                text = name
+                error = '%s: %s' % (e.__class__.__name__, str(e))
+                return text, error
+        else:
+            return name, None
+        
+
+class SourceFile(Column):
+    label = 'Source File'
+    default_width = 350
+
+    def getText(self, d):
+        cfg = d.element.config_file
+        if cfg.package_name:
+            basename = os.path.basename(cfg.filename)
+            if basename == 'configure.zcml':
+                text = cfg.package_name
+            else:
+                text = '%s "%s"' % (cfg.package_name, basename)
+        elif self.project is not None:
+            text = '"%s"' % self.project.shortenPath(cfg.filename)
+        else:
+            text = '"%s"' % cfg.filename
+        if d.element.depth:
+            return '    ' * (d.element.depth) + text
+        else:
+            return text
+
+
+class DirectiveType(Column):
+    label = 'Directive'
+    default_width = 150
+
+    def getText(self, d):
+        ns, localname = d.element.name
+        if interfaces.IGenericNamespaceDirective.providedBy(d):
+            ns = '*'
+        elif ns.startswith(nzo_prefix):
+            ns = ns[len(nzo_prefix):]
+        return '%s:%s' % (ns, localname)
+
+
+class NameOrId(Column):
+    label = 'Name / Id'
+    default_width = 150
+
+    def getText(self, d):
+        data = d.element.data
+        if interfaces.IIncludeDirective.providedBy(d):
+            fn = data.get('files') or data.get('file') or 'configure.zcml'
+            package = data.get('package') or '.'
+            if package:
+                abs_package, error = self.resolve(d, package)
+                if error:
+                    abs_package = '??? %s' % package
+            if abs_package:
+                if fn == 'configure.zcml':
+                    label = abs_package
+                else:
+                    label = '%s "%s"' % (abs_package, fn)
+            else:
+                label = '"%s"' % fn
+            return label
+            
+        res = data.get('name')
+        if res is None:
+            res = data.get('id', '')
+        return res
+
+
+class FactoryClassOrObject(Column):
+    label = 'Factory / Class / Object'
+    default_width = 210
+
+    def getText(self, d):
+        # Search for an object in the directive or any parent
+        src = d.element
+        while src is not None:
+            for attr in ('factory', 'content_factory', 'handler',
+                         'class', 'module', 'schema'):
+                name = src.data.get(attr)
+                if name is not None:
+                    break
+            if name is not None:
+                break
+            src = src.parent
+        if name:
+            text, error = self.resolve(d, name)
+            if src is not d.element:
+                # inherited
+                text = '^ ' + text
+            return text
+        return ''
+
+
+class File(Column):
+    label = 'File'
+    default_width = 80
+
+    def getText(self, d):
+        data = d.element.data
+        name = data.get('file')
+        if name is None:
+            name = data.get('template')
+            if name is None:
+                name = data.get('page')
+        return name or ''
+
+
+def create_default_columns():
+    return [
+        SourceFile(),
+        DirectiveType(),
+        NameOrId(),
+        FactoryClassOrObject(),
+        ]
+##        For(),
+##        Provides(),
+##        Permission(),
+##        InterfaceColumn(),
+##        File(),
+##        ]

Deleted: /z3/zopejam/trunk/src/zopejam/inspector.py
==============================================================================
--- /z3/zopejam/trunk/src/zopejam/inspector.py	Thu Jul  7 10:01:08 2005
+++ (empty file)
@@ -1,78 +0,0 @@
-
-# (c) 2005 Shane Hathaway
-# License: ZPL 2.1
-
-"""This standalone module inspects configured code.
-
-Zope Jam uses this module to provide a degree of isolation between the
-configuration UI and code under development.  Also, this strategy
-makes it possible to re-inspect some of the code without restarting.
-
-"""
-
-import cPickle
-import os
-import sys
-
-from zope.interface.interfaces import IInterface
-
-_import_chickens = {}, {}, ("*",) # dead chickens needed by __import__
-
-
-def inspect_interfaces(names):
-    """Returns {name: {'doc', 'bases', 'attrs' | 'error'}}"""
-    res = {}
-    # the names are absolute.
-    todo = list(names)
-    while todo:
-        name = todo.pop()
-        if res.has_key(name):
-            continue
-        info = {}
-        res[name] = info
-
-        pos = name.rfind('.')
-        if pos >= 0:
-            mname = name[:pos]
-            oname = name[pos + 1:]
-        else:
-            mname = ''
-            oname = name
-
-        try:
-            module = __import__(mname, *_import_chickens)
-            obj = getattr(module, oname)
-        except:
-            info['error'] = str(sys.exc_info()[1])
-            continue
-
-        if IInterface.providedBy(obj):
-            info['doc'] = getattr(obj, '__doc__', None)
-            bases = []
-            for base in getattr(obj, '__bases__', ()):
-                m = getattr(base, '__module__', None)
-                n = getattr(base, '__name__', None)
-                if m and n:
-                    fullname = '%s.%s' % (m, n)
-                    bases.append(fullname)
-                    if not res.has_key(fullname):
-                        todo.append(fullname)
-            info['bases'] = bases
-            attrs = {}
-            for n in obj.names(False):
-                attrs[n] = obj[n]
-            info['attributes'] = attrs
-        else:
-            info['error'] = 'not an interface; type is %s' % str(type(obj))
-    return res
-
-
-def main():
-    # pickle in, pickle out.
-    funcname, args, kwargs = cPickle.load(sys.stdin)
-    func = globals()[funcname]
-    res = func(*args, **kwargs)
-    cPickle.dump(res, sys.stdout, protocol=2)
-
-if __name__ == '__main__':
-    main()

Modified: z3/zopejam/trunk/src/zopejam/main.py
==============================================================================
--- z3/zopejam/trunk/src/zopejam/main.py	(original)
+++ z3/zopejam/trunk/src/zopejam/main.py	Thu Jul  7 10:01:08 2005
@@ -14,7 +14,8 @@
 import wx
 from wx import xrc
 
-from zcmledit.configfile import ConfigurationFile, createBootstrapDefinitions
+from zcmledit.project import Project
+from zopejam import columns
 from zope.schema.interfaces import IField
 
 VERSION = '0.1'
@@ -26,96 +27,6 @@
 add_menu_ids = {}  # {id -> (ns, localname)}
 
 
-class Project:
-
-    def __init__(self, root_filename, interpreter=None, pythonpath=None):
-        self.root_filename = root_filename  # '/path/to/site.zcml'
-        self.base = os.path.dirname(root_filename)
-        if interpreter is None:
-            interpreter = sys.executable
-        self.interpreter = interpreter
-        if pythonpath is None:
-            pythonpath = [os.path.join(self.base, 'src')]
-        self.pythonpath = pythonpath
-
-    def load(self):
-        self.packages = {}  # {package name: [filename]}
-        self.files = {}     # {filename: config_file}
-        # self.definitions contains {(ns, localname): Definition}
-        self.definitions = createBootstrapDefinitions()
-        self.root_config = ConfigurationFile(self.root_filename)
-        self.files[self.root_filename] = self.root_config
-
-        todo = [self.root_config]
-        while todo:
-            cfg = todo.pop(0)
-            cfg.load(self.pythonpath)
-
-            # add to self.packages
-            filenames = self.packages.get(cfg.package_name)
-            if filenames is None:
-                filenames = []
-                self.packages[cfg.package_name] = filenames
-            filenames.append(cfg.filename)
-
-            # add to self.files
-            for resolver in cfg.includes:
-                for fn in resolver.listFiles():
-                    if not self.files.has_key(fn):
-                        subcfg = ConfigurationFile(
-                            fn, resolver.getAbsolutePackageName())
-                        self.files[fn] = subcfg
-                        todo.append(subcfg)
-
-            # add to self.definitions
-            for dfn in cfg.definitions:
-                if self.definitions.has_key(dfn.fullname):
-                    raise RuntimeError('conflict on %s' % repr(dfn.fullname))
-                self.definitions[dfn.fullname] = dfn
-
-        # load schemas
-        names = [dfn.abs_schema for dfn in self.definitions.values()]
-        self.schemas = self.call_inspector('inspect_interfaces', names)
-
-        # self.print_fields()
-
-    def call_inspector(self, funcname, *args, **kw):
-        inspector_py = os.path.join(here, 'inspector.py')
-        env = os.environ.copy()
-        env['PYTHONPATH'] = os.pathsep.join(self.pythonpath)
-        p = subprocess.Popen(
-            [self.interpreter, inspector_py],
-            stdin=subprocess.PIPE,
-            stdout=subprocess.PIPE,
-            env=env)
-        cPickle.dump((funcname, args, kw), p.stdin, protocol=2)
-        res_pickle = p.stdout.read()
-        exit_code = p.wait()
-        if exit_code:
-            raise RuntimeError(
-                "inspector failed with exit code %s" % exit_code)
-        res = cPickle.loads(res_pickle)
-        return res
-
-    def print_fields(self):
-        """Shows how well fields are reused by different directive types."""
-        fields = {}  # {(field_name, field_type) -> [schema_name]}
-        for schema_name, info in self.schemas.items():
-            if not info.has_key('attributes'):
-                continue
-            for attr_name, obj in info['attributes'].items():
-                if not IField.providedBy(obj):
-                    continue
-                key = (attr_name, type(obj))
-                lst = fields.get(key)
-                if lst is None:
-                    lst = []
-                    fields[key] = lst
-                lst.append(schema_name)
-        import pprint
-        pprint.pprint(fields)
-
-
 class PaletteImageList(wx.ImageList):
 
     def __init__(self, w, h):
@@ -178,8 +89,14 @@
         pkgtree = xrc.XRCCTRL(self.frame, 'package_tree')
         pkgtree.SetImageList(self.small_palette_images)
 
-        self.alldirs = DirectiveList(
-            self, xrc.XRCCTRL(self.frame, 'directive_list_panel'))
+        # Replace the directive list with a DirectiveList instance
+        old_dir_list = xrc.XRCCTRL(self.frame, 'dir_list')
+        parent = old_dir_list.GetParent()
+        id = old_dir_list.GetId()
+        old_dir_list.Destroy()
+        self.dir_list = DirectiveList(self, parent, id)
+        self.dir_list.SetBackgroundColour(wx.WHITE)
+        parent.GetSizer().Add(self.dir_list, flag=wx.EXPAND)
 
         self.tip_frame = resources.LoadFrame(self.frame, 'tip_frame')
         self.tip_frame.Fit()
@@ -374,7 +291,7 @@
                 if package:
                     label = os.path.basename(filename)
                 else:
-                    label = self.shortenPath(filename)
+                    label = self.project.shortenPath(filename)
                 pkgtree.AppendItem(pkgid, label, image=file_image)
 
         # prepare reverse_menu_ids
@@ -412,6 +329,8 @@
                 m.Append(item_id, name)
             addmenu.AppendMenu(-1, label, m)
 
+        self.dir_list.populate()
+
 
     def onMenuHighlight(self, event):
         item_id = event.GetId()
@@ -479,25 +398,44 @@
                 return p[len_base + 1:]
         return p
 
-class DirectiveList:
 
-    def __init__(self, gui, panel):
+class DirectiveList(wx.ListCtrl):
+
+    def __init__(self, gui, parent, id):
+        wx.ListCtrl.__init__(
+            self, parent, id, style=wx.LC_REPORT | wx.LC_VIRTUAL)
         self.gui = gui
-        self.panel = panel
+        self.elements = []
 
-        self.filter = Filter(gui, xrc.XRCCTRL(panel, 'filter_panel'))
+        self.filter = Filter(gui, xrc.XRCCTRL(self.gui.frame, 'filter_panel'))
+        self.columns = columns.create_default_columns()
 
-        self.listctrl = xrc.XRCCTRL(panel, 'dir_list')
-        self.listctrl.InsertColumn(0, 'Source File', width=125)
-        self.listctrl.InsertColumn(1, 'Directive', width=100)
-        self.listctrl.InsertColumn(2, 'Name / Id', width=80)
-        self.listctrl.InsertColumn(3, 'For', width=100)
-        self.listctrl.InsertColumn(4, 'Provides', width=100)
-        self.listctrl.InsertColumn(5, 'Factory / Class', width=100)
-        self.listctrl.InsertColumn(6, 'Permission', width=80)
-        self.listctrl.InsertColumn(7, 'Interface', width=80)
-        self.listctrl.InsertColumn(8, 'File', width=80)
-        self.listctrl.InsertStringItem(0, 'test')
+        for i, c in enumerate(self.columns):
+            self.InsertColumn(i, c.label, width=c.default_width)
+
+    def populate(self):
+        for c in self.columns:
+            c.setProject(self.gui.project)
+
+        if self.gui.project is None:
+            self.SetItemCount(0)
+            return
+
+        self.elements = []
+        files = []
+        for cfg in self.gui.project.files.values():
+            # sort by source file
+            files.append(((cfg.package_name or '', cfg.filename), cfg))
+        files.sort()
+        for sort_key, cfg in files:
+            if cfg.root is not None:
+                self.elements.extend(cfg.root.flattened())
+        self.SetItemCount(len(self.elements))
+
+    def OnGetItemText(self, item, column):
+        d = self.elements[item].directive
+        c = self.columns[column]
+        return c.getText(d)
 
 
 class Filter:
@@ -646,3 +584,4 @@
 
 if __name__ == '__main__':
     main()
+

Modified: z3/zopejam/trunk/src/zopejam/notes.txt
==============================================================================
--- z3/zopejam/trunk/src/zopejam/notes.txt	(original)
+++ z3/zopejam/trunk/src/zopejam/notes.txt	Thu Jul  7 10:01:08 2005
@@ -1,5 +1,21 @@
 
 
+Filter types:
+
+  - Directives containing text
+  - Directives touching file X
+  - Directives touching class / object X
+
+
+TODO:
+
+  - embed the resolver in the directive so that the directive list can
+  reuse the resolver.
+
+  - start to work on properties panel
+
+
+
 Interesting Columns:
 
   Description (MessageID or TextLine)

Modified: z3/zopejam/trunk/src/zopejam/resources.xrc
==============================================================================
--- z3/zopejam/trunk/src/zopejam/resources.xrc	(original)
+++ z3/zopejam/trunk/src/zopejam/resources.xrc	Thu Jul  7 10:01:08 2005
@@ -203,7 +203,7 @@
                 <object class="wxNotebook" name="files_notebook">
                   <object class="notebookpage">
                     <label>Directives</label>
-                    <object class="wxPanel" name="directive_list_panel">
+                    <object class="wxPanel" name="">
                       <object class="wxFlexGridSizer">
                         <object class="sizeritem">
                           <object class="wxFlexGridSizer">
@@ -261,27 +261,20 @@
                         <object class="sizeritem">
                           <object class="wxPanel">
                             <object class="wxFlexGridSizer">
-                              <cols>2</cols>
-                              <object class="sizeritem">
-                                <object class="wxBitmapButton" name="select_columns_button">
-                                  <bitmap>icons/other/configure_toolbars.png</bitmap>
-                          </object>
-                        </object>
+                              <cols>1</cols>
+                              <growablerows>0</growablerows>
+                              <growablecols>0</growablecols>
                               <object class="sizeritem">
                                 <object class="wxListCtrl" name="dir_list">
-                                  <style>wxLC_REPORT</style>
+                                  <style>wxLC_REPORT | wxLC_VIRTUAL</style>
                                   <bg>#FFFFFF</bg>
                             </object>
                                 <flag>wxEXPAND</flag>
                 </object>
-                              <growablerows>0</growablerows>
-                              <growablecols>1</growablecols>
                             </object>
-                            <style>wxSUNKEN_BORDER</style>
-                            <bg>#FFFFFF</bg>
                           </object>
                           <flag>wxEXPAND</flag>
-                    </object>
+                        </object>
                       </object>
                     </object>
               </object>
@@ -781,19 +774,31 @@
     <style>wxFRAME_NO_TASKBAR|wxNO_BORDER</style>
     <object class="wxFlexGridSizer">
       <cols>1</cols>
+      <growablerows>0</growablerows>
+      <growablecols>0</growablecols>
       <object class="sizeritem">
-        <object class="wxTextCtrl" name="tip_text">
-          <value>abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef </value>
-          <style>wxTE_MULTILINE|wxNO_BORDER</style>
+        <object class="wxPanel">
+          <object class="wxFlexGridSizer">
+            <cols>1</cols>
+            <object class="sizeritem">
+              <object class="wxTextCtrl" name="tip_text">
+                <value>abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef </value>
+                <style>wxTE_MULTILINE|wxNO_BORDER</style>
+                <bg>#FFFFC0</bg>
+        </object>
+              <flag>wxALL|wxEXPAND</flag>
+              <border>4</border>
+              <minsize>400, 400</minsize>
+      </object>
+            <growablerows>0</growablerows>
+            <growablecols>0</growablecols>
+          </object>
           <bg>#FFFFC0</bg>
         </object>
         <flag>wxALL|wxEXPAND</flag>
-        <border>4</border>
-        <minsize>400, 400</minsize>
+        <border>1</border>
       </object>
-      <growablerows>0</growablerows>
-      <growablecols>0</growablecols>
     </object>
-    <bg>#FFFFC0</bg>
+    <bg>#000000</bg>
   </object>
 </resource>


More information about the z3-checkins mailing list