[cig-commits] r5070 - cs/merlin/trunk/merlin

leif at geodynamics.org leif at geodynamics.org
Fri Oct 20 11:39:09 PDT 2006


Author: leif
Date: 2006-10-20 11:39:09 -0700 (Fri, 20 Oct 2006)
New Revision: 5070

Added:
   cs/merlin/trunk/merlin/builtins.cfg
   cs/merlin/trunk/merlin/classes.py
   cs/merlin/trunk/merlin/defaults.cfg
   cs/merlin/trunk/merlin/objects.py
   cs/merlin/trunk/merlin/parser.py
   cs/merlin/trunk/merlin/properties.py
Modified:
   cs/merlin/trunk/merlin/__init__.py
Log:
More work on the Merlin project parser.  I don't know if this will be
ready in a reasonable time frame (and I'm not sure I even like .cfg
syntax for project files), so I'm going to punt Merlin's advanced
Pythonic project parser technology for now.


Modified: cs/merlin/trunk/merlin/__init__.py
===================================================================
--- cs/merlin/trunk/merlin/__init__.py	2006-10-20 08:19:58 UTC (rev 5069)
+++ cs/merlin/trunk/merlin/__init__.py	2006-10-20 18:39:09 UTC (rev 5070)
@@ -42,331 +42,14 @@
 #
 
 
-### don't forget, .svn metadata gone in tarball; need gen manifest/sources
-### imports.people?  Requires decoration
-### how to tie to filesystem?  svn:metadata?
-### implicit roots for traits
-####### style:  requires-imports?
-### exports, (freeze-and-?)include in project
-
-### food for thought:
-###### projects have implicit "root = ${thisfile}.${directory}"
-###### "mounting" stuff... almost nothing has to be hard-coded in merlin itself
-
-
-__inUserSpace__ = False
-
-
-class Node(object):
-
-    class UserAttributes(object):
-        
-        def __init__(self):
-            self._factory = None
-            self._frozen = False
-
-        def __getattribute__(self, attr):
-            xgetattr = super(Node.UserAttributes, self).__getattribute__
-            if (attr.startswith('_') or
-                self._frozen or
-                self._factory is None):
-                value = xgetattr(attr)
-            else:
-                try:
-                    value = xgetattr(attr)
-                except AttributeError:
-                    value = self._createNewAttribute(attr)
-            return value
-
-        def _createNewAttribute(self, attr):
-            value = self._factory(attr)
-            setattr(self, attr, value)
-            value._name = attr
-            return value
-
-    isContextual = False
-
-    def __init__(self, name):
-        super(Node, self).__init__()
-        self.name = name
-        self.attrs = Node.UserAttributes()
-        self.attrs._factory = Node
-
-    def __getattribute__(self, attr):
-        global __inUserSpace__, __lookups__
-        if __inUserSpace__:
-            __lookups__.append(attr)
-            return self.getUserAttribute(attr)
-        return super(Node, self).__getattribute__(attr)
-
-    def getFactory(self): return self.attrs._factory
-    def setFactory(self, value): self.attrs._factory = value
-    factory = property(getFactory, setFactory)
-
-    def __repr__(self):
-        return self.__class__.__name__
-
-    def getUserAttribute(self, attr): return getattr(self.attrs, attr)
-
-    def setUserAttribute(self, attr, value): setattr(self.attrs, attr, value)
-
-    def freeze(self):
-        self.attrs._frozen = True
-        for name, child in self.children():
-            child.freeze()
-        return
-
-    def resolveAll(self, context):
-        context.push(self)
-        self.resolve(context)
-        context.pop()
-
-    def resolve(self, context):
-        for name, child in self.children():
-            child.resolveAll(context)
-        return
-
-    def populateNamespace(self, namespace):
-        for name, child in self.children():
-            namespace[name] = child
-        return namespace
-
-    def dump(self, indent = 0):
-        i = indent * "    "
-        print "%s%s(%r):" % (i, self.name, self)
-        for name, child in self.children():
-            child.dump(indent + 1)
-        return
-
-    def children(self):
-        for k,v in self.attrs.__dict__.iteritems():
-            if k.startswith('_'):
-                continue
-            yield k,v
-        return
-
-    def find(self, name):
-        return self.follow(name.split('.'))
-
-    def follow(self, path):
-        node = self
-        for attr in path:
-            node = node.getUserAttribute(attr)
-        return node
-
-
-class LeafNode(Node):
-    def dump(self, indent = 0):
-        i = indent * "    "
-        print "%s%s = %r" % (i, self.name, self)
-
-
-class Literal(LeafNode):
-    
-    def __init__(self, name, value):
-        super(Literal, self).__init__(name)
-        self._value = value
-        
-    def __repr__(self):
-        return repr(self._value)
-
-
-class SymbolicExpr(LeafNode):
-    
-    def __init__(self, name, expr):
-        super(SymbolicExpr, self).__init__(name)
-        self._expr = expr
-        self._value = None
-        
-    def __repr__(self):
-        return repr(self._expr)
-    
-    def resolve(self, context):
-        self._value = context.resolve(self)
-
-
-class ErrorNode(LeafNode):
-    
-    def __init__(self, name, exception):
-        super(ErrorNode, self).__init__(name)
-        self._exception = exception
-        
-    def __repr__(self):
-        return repr(self._exception)
-
-
-class Object(Node): pass
-
-
-class Action(Object): pass
-class Component(Object): pass
-class Program(Object): pass
-class Library(Object): pass
-
-class Person(Object): pass
-
-
-class Project(Object):
-
-    isContextual = True
-
-    def __init__(self, name):
-        super(Project, self).__init__(name)
-        self.attrs.actions.factory = Action
-        self.attrs.components.factory = Component
-        self.attrs.programs.factory = Program
-        self.attrs.libraries.factory = Library
-
-
-class Root(Object):
-    isContextual = True
-    def __init__(self, name="root"):
-        super(Root, self).__init__(name)
-
-
-class Context(object):
-
-    def __init__(self, root):
-        super(Context, self).__init__()
-        self.path = []
-        self.root = root
-        self.errors = False
-        self.namespaceStack = [dict()] # pre-root namespace
-
-    def push(self, node):
-        self.path.append(node)
-        if node.isContextual:
-            new = dict(self.currentNamespace())
-            node.populateNamespace(new)
-            self.namespaceStack.append(new)
-        return
-
-    def pop(self):
-        node = self.path.pop()
-        if node.isContextual:
-            self.namespaceStack.pop()
-        return node
-
-    def currentPathName(self): return '.'.join([node.name for node in self.path[1:]])
-
-    def resolveAll(self):
-        self.root.resolveAll(self)
-
-    def resolve(self, symbol):
-        value, exception = self.lookup(symbol._expr)
-        if exception:
-            value = [ErrorNode(symbol.name, exception)]
-            self.unresolvedSymbol(symbol, exception)
-        return value
-
-    def lookup(self, identifier):
-        value = None
-        exception = None
-        userSpace = dict(globals())
-        userSpace['__inUserSpace__'] = True
-        userSpace['__lookups__'] = []
-        try:
-            value = eval(identifier, self.currentNamespace(), userSpace)
-        except Exception, e:
-            exception = e
-        print '@@@@', userSpace['__lookups__']
-        return value, exception
-
-    def currentNamespace(self): return self.namespaceStack[-1]
-    
-    def unresolvedSymbol(self, symbol, exception):
-        import sys
-        
-        self.errors = True
-        name = self.currentPathName()
-        
-        print >> sys.stderr, name
-        print >> sys.stderr,  "    error: cannot resolve '%s':" % symbol._expr
-        print >> sys.stderr,  "        %s" % exception
-        return
-
-
-def parseSpell(filename = 'project.cfg'):
-    
-    from os.path import expanduser, join
-    from ConfigParser import ConfigParser
-
-    # initialize
-    spell = ConfigParser()
-    #spell.readfp(open('defaults.cfg')) # pkg
-    spell.read(expanduser(join('~', '.merlin', 'etc', 'merlin', 'preferences.cfg')))
-
-    # parse the spell file
-    spell.readfp(open(filename))
-    
-    return spell
-
-
-def parseSpellSyntaxTree(spell):
-    """Return an abstract syntax tree for the raw syntax tree <spell>.
-
-    """
-
-    # This function is, in essence, a "tree parser" in the ANTLR
-    # sense.
-
-    from curses.ascii import isalpha
-    
-    ast = Root()
-    ast.attrs.projects.factory = Project
-    ast.attrs.people.factory = Person
-
-    for section in spell.sections():
-
-        # Walk the path given by the section name, starting at the
-        # root.
-        current = ast.find(section)
-        
-        # Walk the path given by each item name, starting at the
-        # current node.
-        items = spell.items(section)
-        for name, value in items:
-            path = name.split('.')
-            node = current.follow(path[:-1])
-            attr = path[-1]
-            
-            if len(value) and (isalpha(value[0]) or value[0] == '['):
-                # These will be evaluated later.
-                value = SymbolicExpr(attr, value)
-            else:
-                value = eval(value)
-                if isinstance(value, basestring):
-                    value = ' '.join(value.splitlines())
-                    value = value.strip()
-                value = Literal(attr, value)
-            node.setUserAttribute(attr, value)
-
-    return ast
-
-
 def setup():
-    from setuptools import setup, find_packages, Extension
+    from parser import parse
+    root = parse()
+    return
 
-    spell = parseSpell()
 
-    ast = parseSpellSyntaxTree(spell)
-    
-    ast.dump()
+def oldsetup():
 
-    # The spell has been cast; no magic node creation beyond this
-    # point.  Perhaps we need some Latin here or something, instead of
-    # 'freeze'.
-    ast.freeze()
-
-    # Fix-up symbolic references.
-    context = Context(ast)
-    context.resolveAll()
-    if context.errors:
-        import sys
-        sys.exit("merlin: error(s) loading project")
-    
-    return
-
     name = config.get('project', 'name')
     version = config.get('project', 'version')
     extensions = config.get('project', 'extensions')

Added: cs/merlin/trunk/merlin/builtins.cfg
===================================================================
--- cs/merlin/trunk/merlin/builtins.cfg	2006-10-20 08:19:58 UTC (rev 5069)
+++ cs/merlin/trunk/merlin/builtins.cfg	2006-10-20 18:39:09 UTC (rev 5070)
@@ -0,0 +1,39 @@
+# merlin builtins
+
+
+# modifiers -- people
+
+[modifiers.author]
+[modifiers.original]
+[modifiers.primary]
+
+
+# modifiers -- components
+
+[modifiers.experimental]
+[modifiers.legacy]
+[modifiers.native]
+[modifiers.optional]
+[modifiers.primitive]
+[modifiers.private]
+[modifiers.scriptable]
+
+
+# PyPI classifiers
+
+[classifiers.Topic.SciEng.Physics]
+python.distutils.setup.classifier = 'Topic :: Scientific/Engineering :: Physics'
+
+
+# licenses
+
+[licenses.BSD]
+python.distutils.setup.license = 'BSD'
+python.distutils.setup.classifier = 'License :: OSI Approved :: BSD License'
+
+[licenses.GPL]
+python.distutils.setup.license = 'GPL'
+python.distutils.setup.classifier = 'License :: OSI Approved :: GNU General Public License (GPL)'
+
+
+# end of file

Added: cs/merlin/trunk/merlin/classes.py
===================================================================
--- cs/merlin/trunk/merlin/classes.py	2006-10-20 08:19:58 UTC (rev 5069)
+++ cs/merlin/trunk/merlin/classes.py	2006-10-20 18:39:09 UTC (rev 5070)
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#                               merlin
+#
+# Copyright (c) 2006, California Institute of Technology
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#
+#    * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+#    * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#
+#    * Neither the name of the California Institute of Technology nor
+#    the names of its contributors may be used to endorse or promote
+#    products derived from this software without specific prior
+#    written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+
+
+from properties import Property
+
+
+class UserObjectClass(type):
+
+
+    def __init__(cls, name, bases, dct):
+        type.__init__(name, bases, dct)
+
+        properties = {}
+
+        if False:
+            bases = list(bases)
+            bases.reverse()
+            for base in bases:
+                try:
+                    properties.update(base._properties)
+                except AttributeError:
+                    pass
+
+        for name, prop in [kv for kv in dct.iteritems()
+                           if isinstance(kv[1], Property)]:
+            prop.name = name
+            properties[prop.name] = prop
+
+        cls._properties = properties
+
+        return
+
+
+# end of file

Added: cs/merlin/trunk/merlin/defaults.cfg
===================================================================
--- cs/merlin/trunk/merlin/defaults.cfg	2006-10-20 08:19:58 UTC (rev 5069)
+++ cs/merlin/trunk/merlin/defaults.cfg	2006-10-20 18:39:09 UTC (rev 5070)
@@ -0,0 +1,2 @@
+# defaults for merlin
+

Added: cs/merlin/trunk/merlin/objects.py
===================================================================
--- cs/merlin/trunk/merlin/objects.py	2006-10-20 08:19:58 UTC (rev 5069)
+++ cs/merlin/trunk/merlin/objects.py	2006-10-20 18:39:09 UTC (rev 5070)
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#                               merlin
+#
+# Copyright (c) 2006, California Institute of Technology
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#
+#    * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+#    * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#
+#    * Neither the name of the California Institute of Technology nor
+#    the names of its contributors may be used to endorse or promote
+#    products derived from this software without specific prior
+#    written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+
+
+from properties import *
+from classes import UserObjectClass
+
+
+class UserObject(object):
+
+    __metaclass__ = UserObjectClass
+
+    modifiers = ModifierList()
+
+
+class Contact(UserObject):
+
+    email = EMail()
+
+
+class Person(Contact):
+
+    name = String()
+
+
+class MailingList(Contact): pass
+
+
+class Project(UserObject):
+
+    version      = String()
+
+    summary      = String()
+    description  = String()
+    
+    classifiers  = Classifiers()
+    license      = License()
+
+    homepage     = URL()
+    contact      = Contact()
+
+
+class Target(UserObject):
+
+    requires = TargetList()
+
+
+class Library(Target): pass
+class ImportLibrary(Library): pass
+
+
+class ProjectLibrary(Library):
+
+    authors = PersonList()
+
+    #source = SourceCode()
+
+
+# end of file

Copied: cs/merlin/trunk/merlin/parser.py (from rev 5058, cs/merlin/trunk/merlin/__init__.py)
===================================================================
--- cs/merlin/trunk/merlin/__init__.py	2006-10-17 09:11:50 UTC (rev 5058)
+++ cs/merlin/trunk/merlin/parser.py	2006-10-20 18:39:09 UTC (rev 5070)
@@ -0,0 +1,488 @@
+#!/usr/bin/env python
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#                               merlin
+#
+# Copyright (c) 2006, California Institute of Technology
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#
+#    * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+#    * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#
+#    * Neither the name of the California Institute of Technology nor
+#    the names of its contributors may be used to endorse or promote
+#    products derived from this software without specific prior
+#    written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+
+
+### don't forget, .svn metadata gone in tarball; need gen manifest/sources
+### imports.people?  Requires decoration
+### how to tie to filesystem?  svn:metadata?
+### exports, (freeze-and-?)include in project
+
+
+__inUserSpace__ = False
+
+
+root = None
+
+
+class Node(object):
+
+    class UserAttributes(object):
+        
+        def __init__(self):
+            self._factory = None
+            self._frozen = False
+
+        def __getattribute__(self, attr):
+            xgetattr = super(Node.UserAttributes, self).__getattribute__
+            if (attr.startswith('_') or
+                self._frozen or
+                self._factory is None):
+                value = xgetattr(attr)
+            else:
+                try:
+                    value = xgetattr(attr)
+                except AttributeError:
+                    value = self._createNewAttribute(attr)
+            return value
+
+        def _createNewAttribute(self, attr):
+            value = self._factory(attr)
+            setattr(self, attr, value)
+            value._name = attr
+            return value
+
+    isContextual = False
+
+    def __init__(self, name):
+        super(Node, self).__init__()
+        self.name = name
+        self.implicitNamespaceNodes = []
+        self._ = self.UserAttributes()
+        self._._factory = Node
+
+    def __getattribute__(self, attr):
+        global __inUserSpace__
+        if __inUserSpace__:
+            # return self.getUserAttribute(attr)
+            return getattr(super(Node, self).__getattribute__('_'), attr)
+        return super(Node, self).__getattribute__(attr)
+
+    def __repr__(self):
+        return self.__class__.__name__
+
+    def getUserAttribute(self, attr): return getattr(self._, attr)
+
+    def setUserAttribute(self, attr, value): setattr(self._, attr, value)
+
+    def parseAttributeValue(self, attr, value):
+        from curses.ascii import isalpha
+        if len(value) == 0:
+            value = Literal(attr, "")
+        elif isalpha(value[0]) or value[0] == '[':
+            # These will be evaluated later.
+            value = SymbolicExpr(attr, value)
+        else:
+            value = eval(value)
+            if isinstance(value, basestring):
+                value = ' '.join(value.splitlines())
+                value = value.strip()
+            value = Literal(attr, value)
+        self.setUserAttribute(attr, value)
+        return
+
+    def bless(self, attr, value): pass
+
+    def freeze(self):
+        self._._frozen = True
+        for name, child in self.children():
+            child.freeze()
+        return
+
+    def resolveAll(self, context):
+        context.push(self)
+        self.resolve(context)
+        context.pop()
+
+    def resolve(self, context):
+        for name, child in self.children():
+            self.bless(name, child) # bless the child
+            child.resolveAll(context)
+        return
+
+    def populateNamespace(self, namespace):
+        for name, child in self.children():
+            namespace[name] = child
+        return namespace
+
+    def prepareNamespace(self, namespace):
+        for node in self.implicitNamespaceNodes:
+            for name, child in node.children():
+                namespace[name] = child
+        return
+
+    def addToImplicitNamespace(self, node): self.implicitNamespaceNodes.append(node)
+
+    def dump(self, indent = 0):
+        i = indent * "    "
+        print "%s%s(%r):" % (i, self.name, self)
+        for name, child in self.children():
+            child.dump(indent + 1)
+        return
+
+    def children(self):
+        for k,v in self._.__dict__.iteritems():
+            if k.startswith('_'):
+                continue
+            yield k,v
+        return
+
+    def find(self, name):
+        return self.follow(name.split('.'))
+
+    def follow(self, path):
+        node = self
+        for attr in path:
+            node = node.getUserAttribute(attr)
+        return node
+
+
+class LeafNode(Node):
+    def dump(self, indent = 0):
+        i = indent * "    "
+        print "%s%s = %r" % (i, self.name, self)
+
+
+class Literal(LeafNode):
+    
+    def __init__(self, name, value):
+        super(Literal, self).__init__(name)
+        self._value = value
+        
+    def __repr__(self):
+        return repr(self._value)
+
+
+class SymbolicExpr(LeafNode):
+    
+    def __init__(self, name, expr):
+        super(SymbolicExpr, self).__init__(name)
+        self._expr = expr
+        self._value = None
+        
+    def __repr__(self):
+        return repr(self._expr)
+    
+    def resolve(self, context):
+        self._value = context.resolve(self)
+
+
+class Modifier(LeafNode):
+
+    def __init__(self, name):
+        super(Modifier, self).__init__(name)
+
+
+class ErrorNode(LeafNode):
+    
+    def __init__(self, name, exception):
+        super(ErrorNode, self).__init__(name)
+        self._exception = exception
+        
+    def __repr__(self):
+        return repr(self._exception)
+
+
+class Object(Node):
+
+    def bless(self, attr, value):
+        super(Object, self).bless(attr, value)
+        if attr == 'modifiers':
+            value.addToImplicitNamespace(root._.modifiers)
+        elif attr == 'classifiers':
+            value.addToImplicitNamespace(root._.classifiers)
+        return
+
+
+class PersonNode(Object): pass
+class ActionNode(Object): pass
+
+
+class SourceFileList(Object):
+    def __init__(self, name, filenames):
+        super(SourceFileList, self).__init__(name)
+        self.filenames = filenames
+
+
+class SourceDirectory(Object):
+    def __init__(self, name, directory):
+        super(SourceDirectory, self).__init__(name)
+        self.directory = directory
+
+
+class SourceFilesNode(Object):
+
+    def parseAttributeValue(self, attr, value):
+        value = SourceFileList(value.split())
+        self.setUserAttribute(attr, value)
+
+
+class SourceNode(Node):
+
+    def parseAttributeValue(self, attr, value):
+        if attr == 'directory':
+            value = SourceDirectory(attr, value)
+        elif attr == 'files':
+            value = SourceFileList(attr, value)
+        else:
+            super(SourceNode, self).parseAttributeValue(attr, value)
+        self.setUserAttribute(attr, value)
+
+
+class ComponentNode(Object):
+    
+    def __init__(self, name):
+        super(ComponentNode, self).__init__(name)
+        self._.source = SourceNode('source')
+
+    def bless(self, attr, value):
+        super(ComponentNode, self).bless(attr, value)
+        if attr == 'authors':
+            value.addToImplicitNamespace(root._.people)
+        return
+
+
+class ProgramNode(ComponentNode): pass
+class LibraryNode(ComponentNode): pass
+
+
+class ProjectNode(Object):
+
+    isContextual = True
+
+    def __init__(self, name):
+        super(ProjectNode, self).__init__(name)
+
+        #from objects import Component, Program, Library
+        self._.actions._._factory = ActionNode
+        self._.components._._factory = ComponentNode #NodeFactory(Component)
+        self._.programs._._factory = ProgramNode #NodeFactory(Program)
+        self._.libraries._._factory = LibraryNode #NodeFactory(Library)
+
+
+class FSNode(Node):
+
+    def __init__(self, pathname):
+        super(FSNode, self).__init__(pathname)
+        self.pathname = pathname
+        self._._factory = FSNodeFactory(pathname)
+
+
+class FSNodeFactory(object):
+
+    def __init__(self, pathname):
+        super(FSNodeFactory, self).__init__()
+        from os.path import exists
+        assert exists(pathname)
+        self.pathname = pathname
+
+    def __call__(self, name):
+        from os.path import exists, join
+        childPathname = join(self.pathname, name)
+        assert exists(childPathname)
+        return FSNode(childPathname)
+
+
+class Root(Object):
+    isContextual = True
+    def __init__(self, name='root'):
+        super(Root, self).__init__(name)
+
+
+class Context(object):
+
+    def __init__(self, root):
+        super(Context, self).__init__()
+        self.path = []
+        self.root = root
+        self.errors = False
+        self.namespaceStack = [dict()] # pre-root namespace
+
+    def push(self, node):
+        self.path.append(node)
+        if node.isContextual:
+            new = dict(self.currentNamespace())
+            node.populateNamespace(new)
+            self.namespaceStack.append(new)
+        return
+
+    def pop(self):
+        node = self.path.pop()
+        if node.isContextual:
+            self.namespaceStack.pop()
+        return node
+
+    def currentPathName(self): return '.'.join([node.name for node in self.path[1:]])
+
+    def resolveAll(self):
+        self.root.resolveAll(self)
+
+    def resolve(self, symbol):
+        namespace = dict(self.currentNamespace())
+        symbol.prepareNamespace(namespace)
+        value, exception = self.lookup(symbol._expr, namespace)
+        if exception:
+            value = [ErrorNode(symbol.name, exception)]
+            self.unresolvedSymbol(symbol, exception)
+        return value
+
+    def lookup(self, expr, namespace):
+        value = None
+        exception = None
+        
+        # For some reason I don't understand, passing
+        # '__inUserSpace__' into 'eval' as a part of a 'globals'
+        # dictionary doesn't work.  So for now, use the time honored
+        # technique of save & restore.
+        global __inUserSpace__
+        __inUserSpace__ = True
+        
+        try:
+            value = eval(expr, namespace)
+        except Exception, e:
+            exception = e
+
+        __inUserSpace__ = False
+
+        return value, exception
+
+    def currentNamespace(self): return self.namespaceStack[-1]
+    
+    def unresolvedSymbol(self, symbol, exception):
+        import sys
+        
+        self.errors = True
+        name = self.currentPathName()
+        
+        print >> sys.stderr, name
+        print >> sys.stderr,  "    error: cannot resolve '%s':" % symbol._expr
+        print >> sys.stderr,  "        %s" % exception
+        return
+
+
+def parseSpell(filename = 'project.cfg'):
+    
+    from os.path import expanduser, join
+    from ConfigParser import ConfigParser
+    from pkg_resources import resource_stream
+
+    # initialize
+    spell = ConfigParser()
+    for name in ["builtins", "defaults"]:
+        fp = resource_stream(__name__, "%s.cfg" % name)
+        spell.readfp(fp)
+    spell.read(expanduser(join('~', '.merlin', 'etc', 'merlin', 'preferences.cfg')))
+
+    # parse the spell file
+    spell.readfp(open(filename))
+    
+    return spell
+
+
+def parseSpellSyntaxTree(spell):
+    """Return an abstract syntax tree for the raw syntax tree <spell>.
+
+    """
+
+    # This function is, in essence, a "tree parser" in the ANTLR
+    # sense.
+
+    import os
+
+    ast = Root()
+    ast._.projects._._factory = ProjectNode
+    ast._.people._._factory = PersonNode
+    
+    ast._.filesystem._.srcdir._._factory = FSNodeFactory(os.getcwd())
+    ast._.filesystem._._factory = FSNodeFactory('/') # this must be last
+
+    for section in spell.sections():
+
+        # Walk the path given by the section name, starting at the
+        # root.
+        current = ast.find(section)
+        
+        items = spell.items(section)
+        
+        for name, value in items:
+            
+            # Walk the path given by each item name, starting at the
+            # current node.
+            path = name.split('.')
+            node = current.follow(path[:-1])
+            attr = path[-1]
+
+            # Parse each value.
+            node.parseAttributeValue(attr, value)
+
+    return ast
+
+
+def parse():
+    spell = parseSpell()
+
+    ast = parseSpellSyntaxTree(spell)
+    
+    #ast.dump()
+
+    # The spell has been cast; no magic node creation beyond this
+    # point.  Perhaps we need some Latin here or something, instead of
+    # 'freeze'.
+    ast.freeze()
+
+    # The tree is becoming less abstract...
+    global root
+    root = ast
+
+    # Fix-up symbolic references.
+    context = Context(root)
+    context.resolveAll()
+    
+    if context.errors:
+        import sys
+        sys.exit("merlin: error(s) loading project")
+    
+    # The tree is no longer abstract.
+    del ast
+
+
+# end of file

Added: cs/merlin/trunk/merlin/properties.py
===================================================================
--- cs/merlin/trunk/merlin/properties.py	2006-10-20 08:19:58 UTC (rev 5069)
+++ cs/merlin/trunk/merlin/properties.py	2006-10-20 18:39:09 UTC (rev 5070)
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#                               merlin
+#
+# Copyright (c) 2006, California Institute of Technology
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#
+#    * Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+#    * Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials provided
+#    with the distribution.
+#
+#    * Neither the name of the California Institute of Technology nor
+#    the names of its contributors may be used to endorse or promote
+#    products derived from this software without specific prior
+#    written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+
+
+class Property(object):
+
+    def __init__(self, name=None):
+        self.name = name
+
+    def __get__(self, instance, cls=None):
+        if instance is None:
+            return self
+        return getattr(instance, self.name)
+
+    def __set__(self, instance, value):
+        setattr(instance, self.name, value)
+
+    def parseValue(self, value):
+        return eval(value)
+
+    def resolveValue(self, value, context): pass
+
+
+# strings
+
+class String(Property):
+
+    def parseValue(self, value):
+        value = ' '.join(value.splitlines())
+        return value.strip()
+
+class EMail(String): pass
+class URL(String): pass
+
+
+# symbol lists
+
+class SymbolList(Property):
+
+    def parseValue(self, value):
+        return value.split()
+
+    def resolveValue(self, value, context):
+        return context.resolve(self)
+
+class Classifiers(SymbolList): pass
+class License(SymbolList): pass
+class ModifierList(SymbolList): pass
+class TargetList(SymbolList): pass
+class PersonList(SymbolList): pass
+
+
+# end of file



More information about the cig-commits mailing list