[Checkins] SVN: martian/trunk/ Merged the jw-philipp-using-ndir-directives branch.

Philipp von Weitershausen philikon at philikon.de
Sun May 4 10:47:45 EDT 2008


Log message for revision 86375:
  Merged the jw-philipp-using-ndir-directives branch.
  

Changed:
  U   martian/trunk/CHANGES.txt
  U   martian/trunk/src/martian/__init__.py
  U   martian/trunk/src/martian/core.py
  A   martian/trunk/src/martian/core.txt
  D   martian/trunk/src/martian/directive.py
  A   martian/trunk/src/martian/directive.py
  D   martian/trunk/src/martian/directive.txt
  A   martian/trunk/src/martian/directive.txt
  D   martian/trunk/src/martian/ndir.py
  D   martian/trunk/src/martian/ndir.txt
  D   martian/trunk/src/martian/tests/directive/
  A   martian/trunk/src/martian/tests/scan_for_classes.txt
  A   martian/trunk/src/martian/tests/scanforclasses/
  U   martian/trunk/src/martian/tests/test_all.py
  U   martian/trunk/src/martian/util.py

-=-
Modified: martian/trunk/CHANGES.txt
===================================================================
--- martian/trunk/CHANGES.txt	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/CHANGES.txt	2008-05-04 14:47:45 UTC (rev 86375)
@@ -4,6 +4,34 @@
 0.9.4 (unreleased)
 ==================
 
+Features changes
+----------------
+
+* Replaced the various directive base classes with a single
+  ``martian.Directive`` base class:
+
+  - The directive scope is now defined with the ``scope`` class
+    attribute using one of ``martian.CLASS``, ``martian.MODULE``,
+    ``martian.CLASS_OR_MODULE``.
+
+  - The type of storage is defined with the ``store`` class attribute
+    using one of ``martian.ONCE``, ``martian.MULTIPLE``,
+    ``martian.DICT``.
+
+  - Directives have now gained the ability to read the value that they
+    have set on a component or module using a ``get()`` method.  The
+    ``class_annotation`` and ``class_annotation_list`` helpers have
+    been removed as a consequence.
+
+* Moved the ``baseclass()`` directive from Grok to Martian.
+
+* Added a ``martian.util.check_provides_one`` helper, in analogy to
+  ``check_implements_one``.
+
+* The ``scan_for_classes`` helper now also accepts an ``interface``
+  argument which allows you to scan for classes based on interface
+  rather than base classes.
+
 Bug fixes
 ---------
 

Modified: martian/trunk/src/martian/__init__.py
===================================================================
--- martian/trunk/src/martian/__init__.py	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/__init__.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,4 +1,11 @@
-from core import ModuleGrokker, MultiGrokker, MetaMultiGrokker, grok_dotted_name,\
-     grok_package, grok_module
-from components import GlobalGrokker, ClassGrokker, InstanceGrokker
-from util import scan_for_classes
+from martian.core import (
+    ModuleGrokker, MultiGrokker, MetaMultiGrokker, grok_dotted_name,
+    grok_package, grok_module)
+from martian.components import GlobalGrokker, ClassGrokker, InstanceGrokker
+from martian.util import scan_for_classes
+from martian.directive import Directive, MarkerDirective, MultipleTimesDirective
+from martian.directive import ONCE, MULTIPLE, DICT
+from martian.directive import CLASS, CLASS_OR_MODULE, MODULE
+from martian.directive import (
+    validateText, validateInterface, validateInterfaceOrClass)
+from martian.directive import baseclass

Modified: martian/trunk/src/martian/core.py
===================================================================
--- martian/trunk/src/martian/core.py	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/core.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -85,7 +85,10 @@
 
         # try to grok everything in module
         for name in dir(module):
-            if name.startswith('__grok_'):
+            if '.' in name:
+                # This must be a module-level variable that couldn't
+                # have been set by the developer.  It must have been a
+                # module-level directive.
                 continue
             obj = getattr(module, name)
             if not util.defined_locally(obj, module.__name__):

Copied: martian/trunk/src/martian/core.txt (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/core.txt)
===================================================================
--- martian/trunk/src/martian/core.txt	                        (rev 0)
+++ martian/trunk/src/martian/core.txt	2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,47 @@
+(Edge-case) tests of Martian core components
+============================================
+
+ModuleGrokker ignores values set by directives
+----------------------------------------------
+
+Consider the following module-level directive:
+
+  >>> import martian
+  >>> class store(martian.Directive):
+  ...     scope = martian.MODULE
+  ...     store = martian.ONCE
+  ...
+  >>> store.__module__ = 'somethingelse'  # just so that it isn't __builtin__
+
+Now let's look at a module that contains a simple function and a call
+to the directive defined above:
+
+  >>> class module_with_directive(FakeModule):
+  ...     fake_module = True
+  ...
+  ...     def some_function():
+  ...         return 11
+  ...
+  ...     store(some_function)
+  ...
+  >>> module_with_directive = fake_import(module_with_directive)
+
+Now imagine we have the following grokker for functions:
+
+  >>> import types
+  >>> class FunctionGrokker(martian.InstanceGrokker):
+  ...     component_class = types.FunctionType
+  ...     def grok(self, name, obj, **kw):
+  ...         print name, obj()
+  ...         return True
+  ...
+  >>> module_grokker = martian.ModuleGrokker()
+  >>> module_grokker.register(FunctionGrokker())
+
+and let it loose on the module, we see that it will only find functions
+set by regular variable assignment, not the ones stored by the
+directive:
+
+  >>> module_grokker.grok('module_with_directive', module_with_directive)
+  some_function 11
+  True

Deleted: martian/trunk/src/martian/directive.py
===================================================================
--- martian/trunk/src/martian/directive.py	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/directive.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,202 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006-2007 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Grok directives.
-"""
-
-import sys
-import inspect
-
-from zope import interface
-from zope.interface.interfaces import IInterface
-
-from martian import util
-from martian.error import GrokImportError
-
-class IDirectiveContext(interface.Interface):
-    description = interface.Attribute("The correct place in which the "
-                                      "directive can be used.")
-
-    def matches(frame):
-        """returns whether the given frame is the correct place in
-        which the directive can be used.
-        """
-
-class ClassDirectiveContext(object):
-    interface.implements(IDirectiveContext)
-    
-    description = "class"
-
-    def matches(self, frame):
-        return util.frame_is_class(frame)
-
-    
-class ModuleDirectiveContext(object):
-    interface.implements(IDirectiveContext)
-    
-    description = "module"
-
-    def matches(self, frame):
-        return util.frame_is_module(frame)
-    
-
-class ClassOrModuleDirectiveContext(object):
-    interface.implements(IDirectiveContext)
-    
-    description = "class or module"
-
-    def matches(self, frame):
-        return util.frame_is_module(frame) or util.frame_is_class(frame)
-
-
-class Directive(object):
-    """
-    Directive sets a value into the context's locals as __<name>__
-    ('.' in the name are replaced with '_').
-    """
-
-    def __init__(self, name, directive_context):
-        self.name = name
-        self.local_name = '__%s__' % name.replace('.', '_')
-        self.directive_context = directive_context
-
-    def __call__(self, *args, **kw):
-        self.check_argument_signature(*args, **kw)
-        self.check_arguments(*args, **kw)
-
-        frame = sys._getframe(1)
-        self.check_directive_context(frame)
-
-        value = self.value_factory(*args, **kw)
-        return self.store(frame, value)
-
-    def check_arguments(self, *args, **kw):
-        raise NotImplementedError
-
-    # to get a correct error message, we construct a function that has
-    # the same signature as check_arguments(), but without "self".
-    def check_argument_signature(self, *arguments, **kw):
-        args, varargs, varkw, defaults = inspect.getargspec(
-            self.check_arguments)
-        argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults)
-        exec("def signature_checker" + argspec + ": pass")
-        try:
-            signature_checker(*arguments, **kw)
-        except TypeError, e:
-            message = e.args[0]
-            message = message.replace("signature_checker()", self.name)
-            raise TypeError(message)
-
-    def check_directive_context(self, frame):
-        if not self.directive_context.matches(frame):
-            raise GrokImportError("%s can only be used on %s level."
-                                  % (self.name,
-                                     self.directive_context.description))
-
-    def value_factory(self, *args, **kw):
-        raise NotImplementedError
-
-    def store(self, frame, value):
-        raise NotImplementedError
-
-
-class OnceDirective(Directive):
-    def store(self, frame, value):
-        if self.local_name in frame.f_locals:
-            raise GrokImportError("%s can only be called once per %s."
-                                  % (self.name,
-                                     self.directive_context.description))
-        frame.f_locals[self.local_name] = value
-
-
-class MarkerDirective(OnceDirective):
-    """A directive without argument that places a marker.
-    """
-    def value_factory(self):
-        return True
-
-    def check_arguments(self):
-        pass
-
-class MultipleTimesDirective(Directive):
-    def store(self, frame, value):
-        values = frame.f_locals.get(self.local_name, [])
-        values.append(value)
-        frame.f_locals[self.local_name] = values
-
-
-class SingleValue(object):
-
-    # Even though the value_factory is called with (*args, **kw),
-    # we're safe since check_arguments would have bailed out with a
-    # TypeError if the number arguments we were called with was not
-    # what we expect here.
-    def value_factory(self, value):
-        return value
-
-class OptionalValueDirective(object):
-    def check_arguments(self, value=None):
-        pass
-
-    def value_factory(self, value=None):
-        if value is None:
-            return self.default_value()
-        return value
-
-    def default_value(self):
-        raise NotImplementedError
-
-class BaseTextDirective(object):
-    """
-    Base directive that only accepts unicode/ASCII values.
-    """
-
-    def check_arguments(self, value):
-        if util.not_unicode_or_ascii(value):
-            raise GrokImportError("You can only pass unicode or ASCII to "
-                                  "%s." % self.name)
-
-
-class SingleTextDirective(BaseTextDirective, SingleValue, OnceDirective):
-    """
-    Directive that accepts a single unicode/ASCII value, only once.
-    """
-
-
-class MultipleTextDirective(BaseTextDirective, SingleValue,
-                            MultipleTimesDirective):
-    """
-    Directive that accepts a single unicode/ASCII value, multiple times.
-    """
-
-
-class InterfaceOrClassDirective(SingleValue, OnceDirective):
-    """
-    Directive that only accepts classes or interface values.
-    """
-
-    def check_arguments(self, value):
-        if not (IInterface.providedBy(value) or util.isclass(value)):
-            raise GrokImportError("You can only pass classes or interfaces to "
-                                  "%s." % self.name)
-
-
-class InterfaceDirective(SingleValue, OnceDirective):
-    """
-    Directive that only accepts interface values.
-    """
-
-    def check_arguments(self, value):
-        if not (IInterface.providedBy(value)):
-            raise GrokImportError("You can only pass interfaces to "
-                                  "%s." % self.name)

Copied: martian/trunk/src/martian/directive.py (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/directive.py)
===================================================================
--- martian/trunk/src/martian/directive.py	                        (rev 0)
+++ martian/trunk/src/martian/directive.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,220 @@
+import sys
+import inspect
+
+from zope.interface.interfaces import IInterface
+
+from martian import util
+from martian.error import GrokImportError
+
+class StoreOnce(object):
+
+    def set(self, locals_, directive, value):
+        if directive.dotted_name() in locals_:
+            raise GrokImportError(
+                "The '%s' directive can only be called once per %s." %
+                (directive.name, directive.scope.description))
+        locals_[directive.dotted_name()] = value
+
+    def get(self, directive, component, default):
+        return getattr(component, directive.dotted_name(), default)
+
+    def setattr(self, context, directive, value):
+        setattr(context, directive.dotted_name(), value)
+
+ONCE = StoreOnce()
+
+class StoreOnceGetFromThisClassOnly(StoreOnce):
+
+    def get(self, directive, component, default):
+        return component.__dict__.get(directive.dotted_name(), default)
+
+class StoreMultipleTimes(StoreOnce):
+
+    def get(self, directive, component, default):
+        if getattr(component, directive.dotted_name(), default) is default:
+            return default
+
+        if getattr(component, 'mro', None) is None:
+            return getattr(component, directive.dotted_name())
+
+        result = []
+        for base in reversed(component.mro()):
+            list = getattr(base, directive.dotted_name(), default)
+            if list is not default and list not in result:
+                result.append(list)
+
+        result_flattened = []
+        for entry in result:
+            result_flattened.extend(entry)
+        return result_flattened
+
+    def set(self, locals_, directive, value):
+        values = locals_.setdefault(directive.dotted_name(), [])
+        values.append(value)
+
+MULTIPLE = StoreMultipleTimes()
+
+class StoreDict(StoreOnce):
+
+    def get(self, directive, component, default):
+        if getattr(component, directive.dotted_name(), default) is default:
+            return default
+
+        if getattr(component, 'mro', None) is None:
+            return getattr(component, directive.dotted_name())
+
+        result = {}
+        for base in reversed(component.mro()):
+            mapping = getattr(base, directive.dotted_name(), default)
+            if mapping is not default:
+                result.update(mapping)
+        return result
+
+    def set(self, locals_, directive, value):
+        values_dict = locals_.setdefault(directive.dotted_name(), {})
+        try:
+            key, value = value
+        except (TypeError, ValueError):
+            raise GrokImportError(
+                "The factory method for the '%s' directive should return a "
+                "key-value pair." % directive.name)
+        values_dict[key] = value
+
+DICT = StoreDict()
+
+_SENTINEL = object()
+_USE_DEFAULT = object()
+
+class ClassScope(object):
+    description = 'class'
+
+    def check(self, frame):
+        return util.frame_is_class(frame) and not is_fake_module(frame)
+
+CLASS = ClassScope()
+
+class ClassOrModuleScope(object):
+    description = 'class or module'
+
+    def check(self, frame):
+        return util.frame_is_class(frame) or util.frame_is_module(frame)
+
+CLASS_OR_MODULE = ClassOrModuleScope()
+
+class ModuleScope(object):
+    description = 'module'
+
+    def check(self, frame):
+        return util.frame_is_module(frame) or is_fake_module(frame)
+
+MODULE = ModuleScope()
+
+class Directive(object):
+
+    default = None
+
+    def __init__(self, *args, **kw):
+        self.name = self.__class__.__name__
+
+        self.frame = frame = sys._getframe(1)
+        if not self.scope.check(frame):
+            raise GrokImportError("The '%s' directive can only be used on "
+                                  "%s level." %
+                                  (self.name, self.scope.description))
+
+        self.check_factory_signature(*args, **kw)
+
+        validate = getattr(self, 'validate', None)
+        if validate is not None:
+            validate(*args, **kw)
+
+        value = self.factory(*args, **kw)
+
+        self.store.set(frame.f_locals, self, value)
+
+    # To get a correct error message, we construct a function that has
+    # the same signature as check_arguments(), but without "self".
+    def check_factory_signature(self, *arguments, **kw):
+        args, varargs, varkw, defaults = inspect.getargspec(
+            self.factory)
+        argspec = inspect.formatargspec(args[1:], varargs, varkw, defaults)
+        exec("def signature_checker" + argspec + ": pass")
+        try:
+            signature_checker(*arguments, **kw)
+        except TypeError, e:
+            message = e.args[0]
+            message = message.replace("signature_checker()", self.name)
+            raise TypeError(message)
+
+    def factory(self, value):
+        return value
+
+    def get_default(self, component):
+        return self.default
+
+    @classmethod
+    def dotted_name(cls):
+        return cls.__module__ + '.' + cls.__name__
+
+    @classmethod
+    def get(cls, component, module=None):
+        # Create an instance of the directive without calling __init__
+        self = cls.__new__(cls)
+
+        value = self.store.get(self, component, _USE_DEFAULT)
+        if value is _USE_DEFAULT and module is not None:
+            value = self.store.get(self, module, _USE_DEFAULT)
+        if value is _USE_DEFAULT:
+            value = self.get_default(component)
+
+        return value
+
+    @classmethod
+    def set(cls, component, value):
+        # Create an instance of the directive without calling __init__
+        self = cls.__new__(cls)
+        cls.store.setattr(component, self, value)
+
+
+class MultipleTimesDirective(Directive):
+    store = MULTIPLE
+    default = []
+
+
+class MarkerDirective(Directive):
+    store = ONCE
+    default = False
+
+    def factory(self):
+        return True
+
+
+class baseclass(MarkerDirective):
+    scope = CLASS
+    store = StoreOnceGetFromThisClassOnly()
+
+
+def validateText(directive, value):
+    if util.not_unicode_or_ascii(value):
+        raise GrokImportError("The '%s' directive can only be called with "
+                              "unicode or ASCII." % directive.name)
+
+def validateInterfaceOrClass(directive, value):
+    if not (IInterface.providedBy(value) or util.isclass(value)):
+        raise GrokImportError("The '%s' directive can only be called with "
+                              "a class or an interface." % directive.name)
+
+
+def validateInterface(directive, value):
+    if not (IInterface.providedBy(value)):
+        raise GrokImportError("The '%s' directive can only be called with "
+                              "an interface." % directive.name)
+
+
+# this here only for testing purposes, which is a bit unfortunate
+# but makes the tests a lot clearer for module-level directives
+# also unfortunate that fake_module needs to be defined directly
+# in the fake module being tested and not in the FakeModule base class;
+# the system cannot find it on the frame if it is in the base class.
+def is_fake_module(frame):
+    return frame.f_locals.has_key('fake_module')

Deleted: martian/trunk/src/martian/directive.txt
===================================================================
--- martian/trunk/src/martian/directive.txt	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/directive.txt	2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,226 +0,0 @@
-Directives
-==========
-
-A martian directive is a special function call that causes information
-to be set on module or class level. This information is set as a
-Python module is imported, so should do the minimum amount of work (as
-import side-effects are bad). The idea is that this information can
-then be picked up by a martian-based framework during grok time.
-
-Martian has an infrastructure to make it easy to define new
-directives.
-
-Directive contexts
-------------------
-
-If a directive is given ``ModuleDirectiveContext`` it can only work in
-real modules::
-
-  >>> from martian.directive import SingleTextDirective, ModuleDirectiveContext
-  >>> foo = SingleTextDirective('grok.foo', ModuleDirectiveContext())
-
-We cannot show a working example of the ``foo`` directive in this
-doctest as it is not a real module. It would look like this::
-  
-   foo('hello world')
-
-We have placed this code in a real module and will import it::
-
-  >>> from martian.tests.directive import modulecontext
-
-We expect modulecontext to contain a special attribute ``__grok_foo__``
-with the string ``'hello world'``::
-
-  >>> modulecontext.__grok_foo__
-  'hello world'
-
-This directive cannot be used in a class as it's not allowed by its context::
- 
-  >>> class Test(object):
-  ...   foo('hello world')
-  Traceback (most recent call last):
-   ...
-  GrokImportError: grok.foo can only be used on module level.
-
-Now let's define a directive that can only work in classes::
-
-  >>> from martian.directive import ClassDirectiveContext
-  >>> bar = SingleTextDirective('grok.bar', ClassDirectiveContext())
-
-It won't work in a module::
-
-  >>> from martian.tests.directive import classcontextbroken
-  Traceback (most recent call last):
-    ...
-  GrokImportError: grok.bar can only be used on class level.
-
-It will work in a class context::
-
-  >>> class Test(object):
-  ...   bar('hello world')
-  >>> Test.__grok_bar__
-  'hello world'
-
-Now let's define a directive that can be used both on module-level as
-well as on class-level::
-
-  >>> from martian.directive import ClassOrModuleDirectiveContext
-  >>> qux = SingleTextDirective('grok.qux', ClassOrModuleDirectiveContext())
-
-It can be used in a class::
-
-  >>> class Test(object):
-  ...   qux('hello world')
-  >>> Test.__grok_qux__
-  'hello world'
-
-It can also be used in a module::
-
-  >>> from martian.tests.directive import classormodulecontext
-  >>> classormodulecontext.__grok_qux__
-  'hello world'
-
-Calling a directive once or multiple times
-------------------------------------------
-
-Directives can either be called once in a particular context, or
-multiple times. Let's define a type of directive that can only be
-called once::
-  
-  >>> from martian.directive import OnceDirective, SingleValue, BaseTextDirective
-  >>> class MyDirective(BaseTextDirective, SingleValue, OnceDirective):
-  ...   pass
-  >>> hoi = MyDirective('hoi', ClassDirectiveContext())
-
-When we try to use it twice, we get an error::
-
-  >>> class Test(object):
-  ...   hoi('once')
-  ...   hoi('twice')
-  Traceback (most recent call last):
-    ...
-  GrokImportError: hoi can only be called once per class.
-
-This also works for module-level directives::
-
-  >>> from martian.tests.directive import onlyoncemodulecontext
-  Traceback (most recent call last):
-    ...
-  GrokImportError: hoi can only be called once per module.
-
-Now let's define a directive that can be called multiple times::
-
-  >>> from martian.directive import MultipleTimesDirective
-  >>> class MyDirective(BaseTextDirective, SingleValue, MultipleTimesDirective):
-  ...   pass
-  >>> dag = MyDirective('dag', ClassDirectiveContext())
-
-It will allow you to use it multiple times::
- 
-  >>> class Test(object):
-  ...   dag('once')
-  ...   dag('twice')
-
-The underlying annotation will have stored the multiple values::
-
-  >>> Test.__dag__
-  ['once', 'twice']
-
-Directive values
-----------------
-
-A ``BaseTextDirective`` directive accepts unicode or plain ascii values::
-
- >>> class Test(object):
- ...   hoi('hello')
- >>> class Test(object):
- ...   hoi(u'è')
-
-It won't accept values in another encoding::
-
- >>> class Test(object):
- ...   hoi(u'è'.encode('latin-1'))
- Traceback (most recent call last):
-   ...
- GrokImportError: You can only pass unicode or ASCII to hoi.
- >>> class Test(object):
- ...   hoi(u'è'.encode('UTF-8'))
- Traceback (most recent call last):
-   ...
- GrokImportError: You can only pass unicode or ASCII to hoi.
-
-A ``InterfaceOrClassDirective`` only accepts class or interface objects::
-
-  >>> from martian.directive import InterfaceOrClassDirective
-  >>> class MyDirective(InterfaceOrClassDirective, SingleValue, OnceDirective):
-  ...   pass
-  >>> hello = MyDirective('hello', ClassDirectiveContext())
-  >>> class SomeClass(object):
-  ...    pass
-  >>> class Test(object):
-  ...   hello(SomeClass)
-  >>> class SomeOldStyleClass:
-  ...   pass
-  >>> class Test(object):
-  ...   hello(SomeOldStyleClass)
-  >>> from zope.interface import Interface
-  >>> class ISomeInterface(Interface):
-  ...   pass
-  >>> class Test(object):
-  ...   hello(ISomeInterface)
-
-But not anything else::
-  
-  >>> class Test(object):
-  ...   hello(None)
-  Traceback (most recent call last):
-   ...
-  GrokImportError: You can only pass classes or interfaces to hello.
-  >>> class Test(object):
-  ...   hello('foo')
-  Traceback (most recent call last):
-   ...
-  GrokImportError: You can only pass classes or interfaces to hello.
-
-An ``InterfaceDirective`` accepts only interfaces::
-
-  >>> from martian.directive import InterfaceDirective
-  >>> class MyDirective(InterfaceDirective, SingleValue, OnceDirective):
-  ...   pass
-  >>> hello2 = MyDirective('hello2', ClassDirectiveContext())
-  >>> class Test(object):
-  ...   hello2(ISomeInterface)
-
-But not classes::
-
-  >>> class Test(object):
-  ...   hello2(SomeClass)
-  Traceback (most recent call last):
-    ...
-  GrokImportError: You can only pass interfaces to hello2.
-
-An ``OptionalValueDirective`` accepts a single value, but the value
-can be left out. If so, the value will be calculated by a function we
-pass it::
-
-  >>> from martian.directive import OptionalValueDirective
-  >>> class MyDirective(OptionalValueDirective, OnceDirective):
-  ...    def default_value(self):
-  ...       return 4 # the default if no value is given 
-  >>> hello3 = MyDirective('hello3', ClassDirectiveContext())
-
-Let's try the directive with a single value::
-
-  >>> class Test(object):
-  ...   hello3(1)
-  >>> Test.__hello3__
-  1
-
-We can also try the directive without a value::
-
-  >>> class Test(object):
-  ...   hello3()
-  >>> Test.__hello3__
-  4
-
-

Copied: martian/trunk/src/martian/directive.txt (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/directive.txt)
===================================================================
--- martian/trunk/src/martian/directive.txt	                        (rev 0)
+++ martian/trunk/src/martian/directive.txt	2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,561 @@
+Directives New Style
+====================
+
+When grokking a class, the grokking procedure can be informed by
+directives, on a class, or a module. If a directive is absent, the
+system falls back to a default. Here we introduce a general way to
+define these directives, and how to use them to retrieve information
+for a class for use during the grokking procedure.
+
+A simple directive
+------------------
+
+We define a simple directive that sets a description::
+
+  >>> from martian import Directive, CLASS, ONCE
+  >>> class description(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...     default = u''
+
+The name of the directive is ``description``. We specify that the
+directive can only be used in the scope of a class. We also specify it
+can only be used a single time. Finally we define the default in case
+the directive is absent (the empty string).
+
+Now we look at the directive in action::
+
+  >>> class Foo(object):
+  ...    description(u"This is a description")
+
+After setting it, we can use the ``get`` method on the directive to
+retrieve it from the class again::
+
+  >>> description.get(Foo)
+  u'This is a description'
+
+Directives in different namespaces get stored differently. We'll
+define a similar directive in another namespace::
+
+  >>> class description2(description):
+  ...     pass
+
+  >>> class Foo(object):
+  ...     description(u"Description1")
+  ...     description2(u"Description2")
+  >>> description.get(Foo)
+  u'Description1'
+  >>> description2.get(Foo)
+  u'Description2'
+
+If we check the value of a class without the directive, we see the
+default value for that directive, this case the empty unicode string::
+
+  >>> class Foo(object):
+  ...     pass
+  >>> description.get(Foo)
+  u''
+
+In certain cases we need to set a value on a component as if the directive was
+actually used::
+
+  >>> description.set(Foo, u'value as set')
+  >>> description.get(Foo)
+  u'value as set'
+
+Subclasses of the original class will inherit the properties set by the
+directive:
+
+  >>> class Foo(object):
+  ...     description('This is a foo.')
+  ...
+  >>> class Bar(Foo):
+  ...     pass
+  ...
+  >>> description.get(Bar)
+  'This is a foo.'
+
+When we use the directive outside of class scope, we get an error
+message::
+
+  >>> description('Description')
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'description' directive can only be used on class level.
+
+In particular, we cannot use it in a module::
+
+  >>> class testmodule(FakeModule):
+  ...   fake_module = True
+  ...   description("Description")
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'description' directive can only be used on class level.
+
+We cannot use the directive twice in the class scope. If we do so, we
+get an error message as well::
+
+  >>> class Foo(object):
+  ...   description(u"Description1")
+  ...   description(u"Description2")
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'description' directive can only be called once per class.
+
+We cannot call the directive with no argument either::
+
+  >>> class Foo(object):
+  ...   description()
+  Traceback (most recent call last):
+    ...
+  TypeError: description takes exactly 1 argument (0 given)
+
+Class and module scope
+----------------------
+
+We define a ``layer`` directive that can be used in class and module
+scope both::
+
+  >>> from martian import CLASS_OR_MODULE
+  >>> class layer(Directive):
+  ...     scope = CLASS_OR_MODULE
+  ...     store = ONCE
+
+By default, the ``default`` property is None which is why we can omit
+specifying it here.
+
+We can use this directive now on a class::
+
+  >>> class Foo(object):
+  ...   layer('Test')
+  >>> layer.get(Foo)
+  'Test'
+
+The defaulting to ``None`` works::
+
+  >>> class Foo(object):
+  ...   pass
+  >>> layer.get(Foo) is None
+  True
+
+We can also use it in a module::
+
+  >>> class testmodule(FakeModule):
+  ...    layer('Test2')
+  ...    class Foo(object):
+  ...       pass
+  >>> test_module = fake_import(testmodule)
+
+When we now try to access ``layer`` on ``Foo``, we find the
+module-level default which we just set. We pass the module as the
+second argument to the ``get`` method to have it fall back on this::
+
+  >>> layer.get(testmodule.Foo, testmodule)
+  'Test2'
+
+Let's look at a module where the directive is not used::
+
+  >>> class testmodule(FakeModule):
+  ...   class Foo(object):
+  ...      pass
+  >>> testmodule = fake_import(testmodule)
+
+In this case, the value cannot be found so the system falls back on
+the default, ``None``::
+
+  >>> layer.get(testmodule.Foo, testmodule) is None
+  True
+
+Using a directive multiple times
+--------------------------------
+
+A directive can be configured to allow it to be called multiple times
+in the same scope::
+
+  >>> from martian import MultipleTimesDirective
+  >>> class multi(MultipleTimesDirective):
+  ...     scope = CLASS
+
+We can now use the directive multiple times without any errors::
+
+  >>> class Foo(object):
+  ...   multi(u"Once")
+  ...   multi(u"Twice")
+
+We can now retrieve the value and we'll get a list::
+
+  >>> multi.get(Foo)
+  [u'Once', u'Twice']
+
+The default value for a MultipleTimesDirective is an empty list::
+
+  >>> class Bar(object):
+  ...   pass
+  >>> multi.get(Bar)
+  []
+
+Whenever the directive is used on a sub class of a component, the values set by
+directives on the base classes are combined::
+
+  >>> class Qux(Foo):
+  ...     multi(u'Triple')
+  ...
+  >>> multi.get(Qux)
+  [u'Once', u'Twice', u'Triple']
+
+
+Using a directive multiple times, as a dictionary
+-------------------------------------------------
+
+A directive can be configured to allow it to be called multiple times in the
+same scope. In this case the factory method should be overridden to return a
+key-value pair::
+
+  >>> from martian import DICT
+  >>> class multi(Directive):
+  ...     scope = CLASS
+  ...     store = DICT
+  ...     def factory(self, value):
+  ...         return value.lower(), value
+
+We can now use the directive multiple times without any errors::
+
+  >>> class Bar(object):
+  ...   multi(u"Once")
+  ...   multi(u"Twice")
+
+We can now retrieve the value and we'll get a to the items::
+
+  >>> d = multi.get(Bar)
+  >>> print sorted(d.items())
+  [(u'once', u'Once'), (u'twice', u'Twice')]
+
+When the factory method does not return a key-value pair, an error is raised::
+
+  >>> class wrongmulti(Directive):
+  ...     scope = CLASS
+  ...     store = DICT
+  ...     def factory(self, value):
+  ...         return None
+
+  >>> class Baz(object):
+  ...   wrongmulti(u"Once")
+  Traceback (most recent call last):
+  ...
+  GrokImportError: The factory method for the 'wrongmulti' directive should
+  return a key-value pair.
+
+  >>> class wrongmulti2(Directive):
+  ...     scope = CLASS
+  ...     store = DICT
+  ...     def factory(self, value):
+  ...         return value, value, value
+
+  >>> class Baz(object):
+  ...   wrongmulti2(u"Once")
+  Traceback (most recent call last):
+  ...
+  GrokImportError: The factory method for the 'wrongmulti2' directive should
+  return a key-value pair.
+
+Like with MULTIPLE store, values set by directives using the DICT store are
+combined::
+
+  >>> class multi(Directive):
+  ...     scope = CLASS
+  ...     store = DICT
+  ...     def factory(self, value, andanother):
+  ...         return value, andanother
+  ...
+  >>> class Frepple(object):
+  ...   multi(1, 'AAA')
+  ...   multi(2, 'BBB')
+  ...
+  >>> class Fropple(Frepple):
+  ...   multi(1, 'CCC')
+  ...   multi(3, 'DDD')
+  ...   multi(4, 'EEE')
+
+  >>> d = multi.get(Fropple)
+  >>> print sorted(d.items())
+  [(1, 'CCC'), (2, 'BBB'), (3, 'DDD'), (4, 'EEE')]
+
+Using MULTIPLE and DICT can also work on a module level, even though
+inheritance has no meaning there::
+
+  >>> from martian import MODULE
+  >>> class multi(MultipleTimesDirective):
+  ...     scope = MODULE
+  ...
+  >>> multi.__module__ = 'somethingelse'
+  >>> class module_with_directive(FakeModule):
+  ...     fake_module = True
+  ...
+  ...     multi('One')
+  ...     multi('Two')
+  ...
+  >>> module_with_directive = fake_import(module_with_directive)
+  >>> print multi.get(module_with_directive)
+  ['One', 'Two']
+
+  >>> from martian import MODULE
+  >>> class multi(Directive):
+  ...     scope = MODULE
+  ...     store = DICT
+  ...     def factory(self, value, andanother):
+  ...         return value, andanother
+  ...
+  >>> multi.__module__ = 'somethingelse'
+  >>> class module_with_directive(FakeModule):
+  ...     fake_module = True
+  ...
+  ...     multi(1, 'One')
+  ...     multi(2, 'Two')
+  ...
+  >>> module_with_directive = fake_import(module_with_directive)
+  >>> d = multi.get(module_with_directive)
+  >>> print sorted(d.items())
+  [(1, 'One'), (2, 'Two')]
+
+Calculated defaults
+-------------------
+
+Often instead of just supplying the system with a default, we want to
+calculate the default in some way. We define the ``name`` directive,
+which if not present, will calculate its value from the name of class,
+lower-cased. Instead of passing a default value, we pass a function as the
+default argument::
+
+  >>> class name(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...     def get_default(self, component):
+  ...         return component.__name__.lower()
+
+  >>> class Foo(object):
+  ...   name('bar')
+  >>> name.get(Foo)
+  'bar'
+
+  >>> class Foo(object):
+  ...   pass
+  >>> name.get(Foo)
+  'foo'
+
+A marker directive
+------------------
+
+Another type of directive is a marker directive. This directive takes
+no arguments at all, but when used it marks the context::
+
+  >>> from martian import MarkerDirective
+  >>> class mark(MarkerDirective):
+  ...     scope = CLASS
+
+  >>> class Foo(object):
+  ...     mark()
+
+Class ``Foo`` is now marked::
+
+  >>> mark.get(Foo)
+  True
+
+When we have a class that isn't marked, we get the default value, ``False``::
+
+  >>> class Bar(object):
+  ...    pass
+  >>> mark.get(Bar)
+  False
+
+If we pass in an argument, we get an error::
+
+  >>> class Bar(object):
+  ...   mark("An argument")
+  Traceback (most recent call last):
+    ...
+  TypeError: mark takes no arguments (1 given)
+
+
+Validation
+----------
+
+A directive can be supplied with a validation method. The validation method
+checks whether the value passed in is allowed. It should raise
+``GrokImportError`` if the value cannot be validated, together with a
+description of why not.
+
+First we define our own validation function. A validation function
+takes two arguments:
+
+* the name of the directive we're validating for
+
+* the value we need to validate
+
+The name can be used to format the exception properly.
+
+We'll define a validation method that only expects integer numbers::
+
+  >>> from martian.error import GrokImportError
+  >>> class number(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...     def validate(self, value):
+  ...         if type(value) is not int:
+  ...             raise GrokImportError("The '%s' directive can only be called with an integer." %
+  ...                                   self.name)
+
+  >>> class Foo(object):
+  ...    number(3)
+
+  >>> class Foo(object):
+  ...    number("This shouldn't work")
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'number' directive can only be called with an integer.
+
+Some built-in validation functions
+----------------------------------
+
+Let's look at some built-in validation functions.
+
+The ``validateText`` function determines whether a string
+is unicode or plain ascii::
+
+  >>> from martian import validateText
+  >>> class title(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...     default = u''
+  ...     validate = validateText
+
+When we pass ascii text into the directive, there is no error::
+
+  >>> class Foo(object):
+  ...    title('Some ascii text')
+
+We can also pass in a unicode string without error::
+
+  >>> class Foo(object):
+  ...    title(u'Some unicode text')
+
+Let's now try it with something that's not text at all, such as a number.
+This fails::
+
+  >>> class Foo(object):
+  ...    title(123)
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'title' directive can only be called with unicode or ASCII.
+
+It's not allowed to call the direct with a non-ascii encoded string::
+
+  >>> class Foo(object):
+  ...   title(u'è'.encode('latin-1'))
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'title' directive can only be called with unicode or ASCII.
+
+ >>> class Foo(object):
+ ...   title(u'è'.encode('UTF-8'))
+ Traceback (most recent call last):
+   ...
+ GrokImportError: The 'title' directive can only be called with unicode or ASCII.
+
+The ``validateInterfaceOrClass`` function only accepts class or
+interface objects::
+
+  >>> from martian import validateInterfaceOrClass
+  >>> class klass(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...     validate = validateInterfaceOrClass
+
+It works with interfaces and classes::
+
+  >>> class Bar(object):
+  ...    pass
+  >>> class Foo(object):
+  ...    klass(Bar)
+
+  >>> from zope.interface import Interface
+  >>> class IBar(Interface):
+  ...    pass
+  >>> class Foo(object):
+  ...    klass(IBar)
+
+It won't work with other things::
+
+  >>> class Foo(object):
+  ...   klass(Bar())
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'klass' directive can only be called with a class or an interface.
+
+  >>> class Foo(object):
+  ...   klass(1)
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'klass' directive can only be called with a class or an interface.
+
+The ``validateInterface`` validator only accepts an interface::
+
+  >>> from martian import validateInterface
+  >>> class iface(Directive):
+  ...     scope = CLASS
+  ...     store = ONCE
+  ...     validate = validateInterface
+
+Let's try it::
+
+  >>> class Foo(object):
+  ...    iface(IBar)
+
+It won't work with classes or other things::
+
+  >>> class Foo(object):
+  ...   iface(Bar)
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'iface' directive can only be called with an interface.
+
+  >>> class Foo(object):
+  ...   iface(1)
+  Traceback (most recent call last):
+    ...
+  GrokImportError: The 'iface' directive can only be called with an interface.
+
+Declaring base classes
+----------------------
+
+There's a special directive called 'baseclass' which lets you declare that a
+certain class is the base class for a series of other components.  This
+property should not be inherited by those components.  Consider the following
+base class:
+
+  >>> from martian import baseclass
+  >>> class MyBase(object):
+  ...     baseclass()
+
+As you would expect, the directive will correctly identify this class as a
+baseclass:
+
+  >>> baseclass.get(MyBase)
+  True
+
+But, if we create a subclass of this base class, the subclass won't inherit
+that property, unlike with a regular directive:
+
+  >>> class SubClass(MyBase):
+  ...     pass
+  ...
+  >>> baseclass.get(SubClass)
+  False
+
+Naturally, the directive will also report a false answer if the class doesn't
+inherit from a base class at all and hasn't been marked with the directive:
+
+  >>> class NoBase(object):
+  ...     pass
+  ...
+  >>> baseclass.get(NoBase)
+  False

Deleted: martian/trunk/src/martian/ndir.py
===================================================================
--- martian/trunk/src/martian/ndir.py	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/ndir.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,124 +0,0 @@
-import sys
-
-from zope.interface.interfaces import IInterface
-
-from martian import util
-from martian.error import GrokImportError
-
-# ONCE or MULTIPLE
-ONCE = object()
-MULTIPLE = object()
-
-# arguments
-SINGLE_ARG = object()
-NO_ARG = object()
-OPTIONAL_ARG = object()
-
-_SENTINEL = object()
-_USE_DEFAULT = object()
-
-class ClassScope(object):
-    description = 'class'
-    
-    def check(self, frame):
-        return (util.frame_is_class(frame) and
-                not is_fake_module(frame))
-
-CLASS = ClassScope()
-
-class ClassOrModuleScope(object):
-    description = 'class or module'
-
-    def check(self, frame):
-        return (util.frame_is_class(frame) or
-                util.frame_is_module(frame))
-
-CLASS_OR_MODULE = ClassOrModuleScope()
-
-class Directive(object):
-    def __init__(self, namespace, name, scope, times, default,
-                 validate=None, arg=SINGLE_ARG):
-        self.namespace = namespace
-        self.name = name
-        self.scope = scope
-        self.times = times
-        self.default = default
-        self.validate = validate
-        self.arg = arg
-
-    def __call__(self, value=_SENTINEL):            
-        name = self.namespaced_name()
-
-        if self.arg is NO_ARG:
-            if value is _SENTINEL:
-                value = True
-            else:
-                raise GrokImportError("%s accepts no arguments." % name)
-        elif self.arg is SINGLE_ARG:
-            if value is _SENTINEL:
-                raise GrokImportError("%s requires a single argument." % name)
-        elif self.arg is OPTIONAL_ARG:
-            if value is _SENTINEL:
-                value = _USE_DEFAULT
-
-        if self.validate is not None:
-            self.validate(name, value)
-
-        frame = sys._getframe(1)
-        if not self.scope.check(frame):
-            raise GrokImportError("%s can only be used on %s level." %
-                                  (name, self.scope.description))
-        if self.times is ONCE:
-            if name in frame.f_locals:
-                raise GrokImportError("%s can only be called once per %s." %
-                                      (name, self.scope.description))
-            frame.f_locals[name] = value
-        elif self.times is MULTIPLE:
-            values = frame.f_locals.get(name, [])
-            values.append(value)
-            frame.f_locals[name] = values
-        else:
-            assert False, "Unknown value for times: %" % self.times
-
-    def get(self, component, module=None):
-        name = self.namespaced_name()
-        value = getattr(component, name, _USE_DEFAULT)
-        if value is _USE_DEFAULT and module is not None:
-            value = getattr(module, name, _USE_DEFAULT)
-        if value is _USE_DEFAULT:
-            return self.get_default(component)
-        return value
-
-    def get_default(self, component):
-        if callable(self.default):
-            return self.default(component)
-        return self.default
-
-    def namespaced_name(self):
-        return self.namespace + '.' + self.name
-
-def validateText(name, value):
-    if util.not_unicode_or_ascii(value):
-        raise GrokImportError("%s can only be called with unicode or ASCII." %
-                              name)
-
-def validateInterfaceOrClass(name, value):
-    if not (IInterface.providedBy(value) or util.isclass(value)):
-        raise GrokImportError("%s can only be called with a class or "
-                              "interface." %
-                              name)
-
-
-def validateInterface(name, value):
-    if not (IInterface.providedBy(value)):
-        raise GrokImportError("%s can only be called with an interface." %
-                              name)
-
-    
-# this here only for testing purposes, which is a bit unfortunate
-# but makes the tests a lot clearer for module-level directives
-# also unfortunate that fake_module needs to be defined directly
-# in the fake module being tested and not in the FakeModule base class;
-# the system cannot find it on the frame if it is in the base class.
-def is_fake_module(frame):
-    return frame.f_locals.has_key('fake_module')

Deleted: martian/trunk/src/martian/ndir.txt
===================================================================
--- martian/trunk/src/martian/ndir.txt	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/ndir.txt	2008-05-04 14:47:45 UTC (rev 86375)
@@ -1,417 +0,0 @@
-Directives New Style
-====================
-
-When grokking a class, the grokking procedure can be informed by
-directives, on a class, or a module. If a directive is absent, the
-system falls back to a default. Here we introduce a general way to
-define these directives, and how to use them to retrieve information
-for a class for use during the grokking procedure.
-
-A simple directive
-------------------
-
-We define a simple directive that sets a description::
-
-  >>> from martian.ndir import Directive, CLASS, ONCE
-  >>> description = Directive('martian', 'description', 
-  ...   CLASS, ONCE, u'')
-
-This directive is placed in a namespace (in this case, ``martian``);
-this is just a string and should be used to avoid conflicts between
-directives with the same name that could be defined by different
-packages.
-
-The name of the directive is ``description``. We specify that the
-directive can only be used in the scope of a class. We also specify it
-can only be used a single time. Finally we define the default in case
-the directive is absent (the empty string).
-
-Now we look at the directive in action::
-
-  >>> class Foo(object):
-  ...    description(u"This is a description")
-
-After setting it, we can use the ``get`` method on the directive to
-retrieve it from the class again::
-
-  >>> description.get(Foo)
-  u'This is a description'
-
-Directives in different namespaces get stored differently. We'll
-define a similar directive in another namespace::
-
-  >>> description2 = Directive('different', 'description', 
-  ...   CLASS, ONCE, u'')
-
-  >>> class Foo(object):
-  ...    description(u"Description1")
-  ...    description2(u"Description2")
-  >>> description.get(Foo)
-  u'Description1'
-  >>> description2.get(Foo)
-  u'Description2'
-
-If we check the value of a class without the directive, we see the
-default value for that directive, this case the empty unicode string::
-
-  >>> class Foo(object):
-  ...   pass
-  >>> description.get(Foo)
-  u''
-
-When we use the directive outside of class scope, we get an error
-message::
-
-  >>> description('Description')
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.description can only be used on class level.
-
-In particular, we cannot use it in a module::
-
-  >>> class testmodule(FakeModule):
-  ...   fake_module = True
-  ...   description("Description")
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.description can only be used on class level.
-
-We cannot use the directive twice in the class scope. If we do so, we
-get an error message as well::
-
-  >>> class Foo(object):
-  ...   description(u"Description1")
-  ...   description(u"Description2")
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.description can only be called once per class.
-
-We cannot call the directive with no argument either::
-
-  >>> class Foo(object):
-  ...   description()
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.description requires a single argument.
-
-Class and module scope
-----------------------
-
-We define a ``layer`` directive that can be used in class and module
-scope both::
-
-  >>> from martian.ndir import CLASS_OR_MODULE
-  >>> layer = Directive('martian', 'layer', CLASS_OR_MODULE, ONCE, None)
-
-We can use it on a class::
-
-  >>> class Foo(object):
-  ...   layer('Test')
-  >>> layer.get(Foo)
-  'Test'
-
-The defaulting to ``None`` works::
-
-  >>> class Foo(object):
-  ...   pass
-  >>> layer.get(Foo) is None
-  True
-
-We can also use it in a module::
-
-  >>> class testmodule(FakeModule):
-  ...    layer('Test2')
-  ...    class Foo(object):
-  ...       pass
-  >>> test_module = fake_import(testmodule)
-
-When we now try to access ``layer`` on ``Foo``, we find the
-module-level default which we just set. We pass the module as the
-second argument to the ``get`` method to have it fall back on this::
-
-  >>> layer.get(testmodule.Foo, testmodule)
-  'Test2'
-
-Let's look at a module where the directive is not used::
-
-  >>> class testmodule(FakeModule):
-  ...   class Foo(object):
-  ...      pass
-  >>> testmodule = fake_import(testmodule)
-
-In this case, the value cannot be found so the system falls back on
-the default, ``None``::
-
-  >>> layer.get(testmodule.Foo, testmodule) is None
-  True
-
-Using a directive multiple times
---------------------------------
-
-A directive can be configured to allow it to be called multiple times
-in the same scope::
-
-  >>> from martian.ndir import MULTIPLE
-  >>> multi = Directive('martian', 'multi', CLASS, MULTIPLE, None)
-
-We can now use the directive multiple times without any errors::
-
-  >>> class Foo(object):
-  ...   multi(u"Once")
-  ...   multi(u"Twice")
-
-We can now retrieve the value and we'll get a list::
-
-  >>> multi.get(Foo)
-  [u'Once', u'Twice']
-
-Calculated defaults
--------------------
-
-Often instead of just supplying the system with a default, we want to
-calculate the default in some way. We define the ``name`` directive,
-which if not present, will calculate its value from the name of class,
-lower-cased. Instead of passing a default value, we pass a function as the
-default argument::
-
-  >>> def default(component):
-  ...     return component.__name__.lower()
-  >>> name = Directive('martian', 'name', 
-  ...   CLASS, ONCE, default)
-
-  >>> class Foo(object):
-  ...   name('bar')
-  >>> name.get(Foo)
-  'bar'
-
-  >>> class Foo(object):
-  ...   pass
-  >>> name.get(Foo)
-  'foo'
-
-A marker directive
-------------------
-
-Another type of directive is a marker directive. This directive takes
-no arguments at all, but when used it marks the context::
-
-  >>> from martian.ndir import NO_ARG
-  >>> mark = Directive('martian', 'mark', CLASS, ONCE, False, 
-  ...                  arg=NO_ARG)
-  
-  >>> class Foo(object):
-  ...     mark()
-
-Class ``Foo`` is now marked::
-  
-  >>> mark.get(Foo)
-  True
-
-When we have a class that isn't marked, we get the default value, ``False``::
-
-  >>> class Bar(object):
-  ...    pass
-  >>> mark.get(Bar)
-  False
-
-If we pass in an argument, we get an error::
-
-  >>> class Bar(object):
-  ...   mark("An argument")
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.mark accepts no arguments.
-
-The marker directive also works with calculated defaults::
-
-  >>> def func(component):
-  ...    return component.__name__
-  >>> mark = Directive('martian', 'mark', CLASS, ONCE, func, arg=NO_ARG)
-  >>> class Foo(object):
-  ...    pass
-  >>> mark.get(Foo)
-  'Foo'
-
-Optional arguments
-------------------
-
-We can also construct a directive that receives an optional argument::
-
-  >>> from martian.ndir import OPTIONAL_ARG
-  >>> optional = Directive('martian', 'optional', CLASS, ONCE, 'default',
-  ...                       arg=OPTIONAL_ARG)
-
-We can give it an argument::
-
-  >>> class Foo(object):
-  ...    optional("Hoi")
-  >>> optional.get(Foo)
-  'Hoi'
-
-We can also give it no argument. The default will then be used::
-
-  >>> class Foo(object):
-  ...    optional()
-  >>> optional.get(Foo)
-  'default'
-
-The default will also be used if the directive isn't used at all::
-
-  >>> class Foo(object):
-  ...   pass
-  >>> optional.get(Foo)
-  'default'
-
-The optional value directive also works with calculated defaults::
-
-  >>> optional = Directive('martian', 'optional', CLASS, ONCE, func,
-  ...                       arg=OPTIONAL_ARG)
-  >>> class Foo(object):
-  ...   optional()
-  >>> optional.get(Foo)
-  'Foo'
-  >>> class Foo(object):
-  ...   pass
-  >>> optional.get(Foo)
-  'Foo'
-
-Validation
-----------
-
-A directive can be supplied with a validation function.  validation
-function checks whether the value passed in is allowed. The function
-should raise ``GrokImportError`` if the value cannot be validated,
-together with a description of why not. 
-
-First we define our own validation function. A validation function
-takes two arguments:
-
-* the name of the directive we're validating for
-
-* the value we need to validate
-
-The name can be used to format the exception properly.
-
-We'll define a validation function that only expects integer numbers::
-
-  >>> from martian.error import GrokImportError
-  >>> def validateInt(name, value):
-  ...    if type(value) is not int:
-  ...        raise GrokImportError("%s can only be called with an integer." % 
-  ...                              name)
-
-We use it with a directive::
-
-  >>> number = Directive('martian', 'number', CLASS, ONCE, None, validateInt)
-  >>> class Foo(object):
-  ...    number(3)
-
-  >>> class Foo(object):
-  ...    number("This shouldn't work")
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.number can only be called with an integer.
-
-Some built-in validation functions
-----------------------------------
-
-Let's look at some built-in validation functions.
-
-The ``validateText`` function determines whether a string
-is unicode or plain ascii::
-
-  >>> from martian.ndir import validateText
-  >>> title = Directive('martian', 'title', CLASS, ONCE, u'', validateText)
-
-When we pass ascii text into the directive, there is no error::
-
-  >>> class Foo(object):
-  ...    title('Some ascii text')
-
-We can also pass in a unicode string without error::
-
-  >>> class Foo(object):
-  ...    title(u'Some unicode text')
-
-Let's now try it with something that's not text at all, such as a number. 
-This fails::
-
-  >>> class Foo(object):
-  ...    title(123)
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.title can only be called with unicode or ASCII.
-
-It's not allowed to call the direct with a non-ascii encoded string::
-
-  >>> class Foo(object):
-  ...   title(u'è'.encode('latin-1'))
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.title can only be called with unicode or ASCII.
-
- >>> class Foo(object):
- ...   title(u'è'.encode('UTF-8'))
- Traceback (most recent call last):
-   ...
- GrokImportError: martian.title can only be called with unicode or ASCII.
-
-The ``validateInterfaceOrClass`` function only accepts class or
-interface objects::
-
-  >>> from martian.ndir import validateInterfaceOrClass
-  >>> klass = Directive('martian', 'klass', CLASS, ONCE, None,
-  ...                   validateInterfaceOrClass)
-
-It works with interfaces and classes::
-
-  >>> class Bar(object):
-  ...    pass
-  >>> class Foo(object):
-  ...    klass(Bar)
-
-  >>> from zope.interface import Interface
-  >>> class IBar(Interface):
-  ...    pass
-  >>> class Foo(object):
-  ...    klass(IBar)
-
-It won't work with other things::
-
-  >>> class Foo(object):
-  ...   klass(Bar())
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.klass can only be called with a class or interface.
-
-  >>> class Foo(object):
-  ...   klass(1)
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.klass can only be called with a class or interface.
-
-The ``validateInterface`` validator only accepts an interface::
-
-  >>> from martian.ndir import validateInterface
-  >>> iface = Directive('martian', 'iface', CLASS, ONCE, None,
-  ...                   validateInterface)
-  
-Let's try it::
-
-  >>> class Foo(object):
-  ...    iface(IBar)
-  
-It won't work with classes or other things::
-
-  >>> class Foo(object):
-  ...   iface(Bar)
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.iface can only be called with an interface.
-
-  >>> class Foo(object):
-  ...   iface(1)
-  Traceback (most recent call last):
-    ...
-  GrokImportError: martian.iface can only be called with an interface.
-

Copied: martian/trunk/src/martian/tests/scan_for_classes.txt (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/tests/scan_for_classes.txt)
===================================================================
--- martian/trunk/src/martian/tests/scan_for_classes.txt	                        (rev 0)
+++ martian/trunk/src/martian/tests/scan_for_classes.txt	2008-05-04 14:47:45 UTC (rev 86375)
@@ -0,0 +1,35 @@
+Scanning for the context object
+-------------------------------
+
+Let's import a module that contains no ``Context`` subclass, nor classes
+that implement ``IContext``::
+
+  >>> from martian.tests.scanforclasses import IContext
+
+We shouldn't see any classes that are contexts::
+
+  >>> from martian.util import scan_for_classes
+  >>> from martian.tests.scanforclasses import test1
+  >>> list(scan_for_classes(test1, interface=IContext))
+  []
+
+Now we look at a module with a single ``Context`` subclass::
+
+  >>> from martian.tests.scanforclasses import test2
+  >>> list(scan_for_classes(test2, interface=IContext))
+  [<class 'martian.tests.scanforclasses.test2.MyContext'>]
+
+Now we'll look at a module with a single class that implements ``IContext``::
+
+  >>> from martian.tests.scanforclasses import test3
+  >>> list(scan_for_classes(test3, interface=IContext))
+  [<class 'martian.tests.scanforclasses.test3.MyContext'>]
+
+Let's finish by looking at a module which defines multiple contexts::
+
+  >>> from martian.tests.scanforclasses import test4
+  >>> len(list(scan_for_classes(test4, interface=IContext)))
+  4
+
+
+

Copied: martian/trunk/src/martian/tests/scanforclasses (from rev 86374, martian/branches/jw-philipp-using-ndir-directives/src/martian/tests/scanforclasses)

Modified: martian/trunk/src/martian/tests/test_all.py
===================================================================
--- martian/trunk/src/martian/tests/test_all.py	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/tests/test_all.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -70,10 +70,14 @@
                              optionflags=optionflags),
         doctest.DocFileSuite('directive.txt',
                              package='martian',
+                             globs=globs,
                              optionflags=optionflags),
-        doctest.DocFileSuite('ndir.txt',
+        doctest.DocFileSuite('core.txt',
                              package='martian',
                              globs=globs,
                              optionflags=optionflags),
+        doctest.DocFileSuite('scan_for_classes.txt',
+                             package='martian.tests',
+                             optionflags=optionflags),
         ])
     return suite

Modified: martian/trunk/src/martian/util.py
===================================================================
--- martian/trunk/src/martian/util.py	2008-05-04 14:45:00 UTC (rev 86374)
+++ martian/trunk/src/martian/util.py	2008-05-04 14:47:45 UTC (rev 86375)
@@ -21,6 +21,7 @@
 
 from zope import interface
 
+import martian
 from martian.error import GrokError, GrokImportError
 
 def not_unicode_or_ascii(value):
@@ -48,37 +49,8 @@
     return sys._getframe(2).f_globals['__name__']
 
 def is_baseclass(name, component):
-    return (type(component) is type and
-            class_annotation_nobase(component, 'grok.baseclass', False))
+    return (isclass(component) and martian.baseclass.get(component))
 
-def class_annotation(obj, name, default):
-    return getattr(obj, '__%s__' % name.replace('.', '_'), default)
-
-def class_annotation_nobase(obj, name, default):
-    """This will only look in the given class obj for the annotation.
-
-    It will not look in the inheritance chain.
-    """
-    return obj.__dict__.get('__%s__' % name.replace('.', '_'), default)
-    
-def class_annotation_list(obj, name, default):
-    """This will process annotations that are lists correctly in the face of
-    inheritance.
-    """
-    if class_annotation(obj, name, default) is default:
-        return default
-
-    result = []
-    for base in reversed(obj.mro()):
-        list = class_annotation(base, name, [])
-        if list not in result:
-            result.append(list)
-
-    result_flattened = []
-    for entry in result:
-        result_flattened.extend(entry)
-    return result_flattened
-
 def defined_locally(obj, dotted_name):
     obj_module = getattr(obj, '__grok_module__', None)
     if obj_module is None:
@@ -100,22 +72,38 @@
                         "(use grok.provides to specify which one to use)."
                         % class_, class_)
 
+def check_provides_one(obj):
+    provides = list(interface.providedBy(obj))
+    if len(provides) < 1:
+        raise GrokError("%r must provide at least one interface "
+                        "(use zope.interface.classProvides to specify)."
+                        % obj, obj)
+    if len(provides) > 1:
+        raise GrokError("%r provides more than one interface "
+                        "(use grok.provides to specify which one to use)."
+                        % obj, obj)
 
-def scan_for_classes(module, classes):
+def scan_for_classes(module, classes=None, interface=None):
     """Given a module, scan for classes.
     """
-    result = set()
     for name in dir(module):
-        if name.startswith('__grok_'):
+        if '.' in name:
+            # This must be a module-level variable that couldn't have
+            # been set by the developer.  It must have been a
+            # module-level directive.
             continue
         obj = getattr(module, name)
-        if not defined_locally(obj, module.__name__):
+        if not defined_locally(obj, module.__name__) or not isclass(obj):
             continue
-        for class_ in classes:
-            if check_subclass(obj, class_):
-                result.add(obj)
-    return list(result)
 
+        if classes is not None:
+            for class_ in classes:
+                if check_subclass(obj, class_):
+                    yield obj
+
+        if interface is not None and interface.implementedBy(obj):
+            yield obj
+
 def methods_from_class(class_):
     # XXX Problem with zope.interface here that makes us special-case
     # __provides__.
@@ -128,4 +116,4 @@
     return frame.f_locals is frame.f_globals
 
 def frame_is_class(frame):
-    return '__module__' in frame.f_locals    
+    return '__module__' in frame.f_locals



More information about the Checkins mailing list