浏览代码

Python-style pdef syntax

David Rose 16 年之前
父节点
当前提交
3f5fd50c16
共有 4 个文件被更改,包括 403 次插入635 次删除
  1. 250 477
      direct/src/p3d/Packager.py
  2. 5 4
      direct/src/p3d/packp3d.py
  3. 147 154
      direct/src/p3d/panda3d.pdef
  4. 1 0
      direct/src/p3d/ppackage.py

+ 250 - 477
direct/src/p3d/Packager.py

@@ -173,6 +173,31 @@ class Packager:
         def close(self):
             """ Writes out the contents of the current package. """
 
+            if not self.host:
+                self.host = self.packager.host
+
+            # A special case when building the "panda3d" package.  We
+            # enforce that the version number matches what we've been
+            # compiled with.
+            if self.packageName == 'panda3d':
+                if self.version is None:
+                    self.version = PandaSystem.getPackageVersionString()
+
+                if self.version != PandaSystem.getPackageVersionString():
+                    message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (self.version, PandaSystem.getPackageVersionString())
+                    raise PackageError, message
+
+                if self.host != PandaSystem.getPackageHostUrl():
+                    message = 'mismatched Panda3D host: requested %s, but Panda3D is built as %s' % (self.host, PandaSystem.getPackageHostUrl())
+                    raise PackageError, message
+
+            if self.p3dApplication:
+                # Default compression level for an app.
+                self.compressionLevel = 6
+
+                # Every p3dapp requires panda3d.
+                self.packager.do_require('panda3d')
+
             if self.dryRun:
                 self.multifile = None
             else:
@@ -666,11 +691,7 @@ class Packager:
 
             xpackage.SetAttribute('main_module', self.mainModule[1])
 
-            for variable, value in self.configs.items():
-                if isinstance(value, types.UnicodeType):
-                    xpackage.SetAttribute(variable, value.encode('utf-8'))
-                else:
-                    xpackage.SetAttribute(variable, str(value))
+            self.__addConfigs(xpackage)
 
             for package in self.requires:
                 xrequires = TiXmlElement('requires')
@@ -720,11 +741,7 @@ class Packager:
             if self.version:
                 xpackage.SetAttribute('version', self.version)
 
-            for variable, value in self.configs.items():
-                if isinstance(value, types.UnicodeType):
-                    xpackage.SetAttribute(variable, value.encode('utf-8'))
-                else:
-                    xpackage.SetAttribute(variable, str(value))
+            self.__addConfigs(xpackage)
 
             for package in self.requires:
                 xrequires = TiXmlElement('requires')
@@ -752,6 +769,19 @@ class Packager:
             doc.InsertEndChild(xpackage)
             doc.SaveFile()
 
+        def __addConfigs(self, xpackage):
+            """ Adds the XML config values defined in self.configs to
+            the indicated XML element. """
+            
+            for variable, value in self.configs.items():
+                if isinstance(value, types.UnicodeType):
+                    xpackage.SetAttribute(variable, value.encode('utf-8'))
+                elif isinstance(value, types.BooleanType):
+                    # True or False must be encoded as 1 or 0.
+                    xpackage.SetAttribute(variable, str(int(value)))
+                else:
+                    xpackage.SetAttribute(variable, str(value))
+
         def writeImportDescFile(self):
             """ Makes the package_import.xml file that describes the
             package and its contents, for other packages and
@@ -1350,55 +1380,91 @@ class Packager:
         return None
 
     def readPackageDef(self, packageDef):
-        """ Reads the lines in the .pdef file named by packageDef and
-        dispatches to the appropriate handler method for each
-        line.  Returns the list of package files."""
-
-        self.packageList = []
+        """ Reads the named .pdef file and constructs the packages
+        indicated within it.  Raises an exception if the pdef file is
+        invalid.  Returns the list of packages constructed. """
 
         self.notify.info('Reading %s' % (packageDef))
-        self.inFile = open(packageDef.toOsSpecific())
-        self.lineNum = 0
 
-        # Now start parsing the packageDef lines
-        try:
-            words = self.__getNextLine()
-            while words is not None:
-                command = words[0]
-                try:
-                    methodName = 'parse_%s' % (command)
-                    method = getattr(self, methodName, None)
-                    if method:
-                        method(words)
+        # We use exec to "read" the .pdef file.  This has the nice
+        # side-effect that the user can put arbitrary Python code in
+        # there to control conditional execution, and such.
 
-                    else:
-                        message = 'Unknown command %s' % (command)
-                        raise PackagerError, message
-                except ArgumentError:
-                    message = 'Wrong number of arguments for command %s' %(command)
-                    raise ArgumentError, message
-                except OutsideOfPackageError:
-                    message = '%s command encounted outside of package specification' %(command)
-                    raise OutsideOfPackageError, message
+        # Set up the global and local dictionaries for exec.
+        globals = {}
+        globals['__name__'] = packageDef.getBasenameWoExtension()
+
+        globals['platform'] = PandaSystem.getPlatform()
+
+        # The local dictionary is initially empty, but it will be
+        # filled with all the definitions at the module scope when we
+        # parse the pdef file.
+        locals = {}
+
+        # We'll stuff all of the predefined functions, and both of the
+        # predefined classes, in the global dictionary, so the pdef
+        # file can reference them.
+
+        # By convention, the existence of a method of this class named
+        # do_foo(self) is sufficient to define a pdef method call
+        # foo().
+        for methodName in self.__class__.__dict__.keys():
+            if methodName.startswith('do_'):
+                name = methodName[3:]
+                c = func_closure(name)
+                globals[name] = c.generic_func
 
-                words = self.__getNextLine()
+        globals['p3d'] = class_p3d
+        globals['package'] = class_package
 
+        # Now exec the pdef file.  Assuming there are no syntax
+        # errors, and that the pdef file doesn't contain any really
+        # crazy Python code, all this will do is fill in the
+        # '__statements' list in the module scope.
+        execfile(packageDef.toOsSpecific(), globals, locals)
+
+        packages = []
+
+        # Now iterate through the statements and operate on them.
+        try:
+            for (lineno, stype, name, args, kw) in locals['__statements']:
+                if stype == 'class':
+                    classDef = locals[name]
+                    p3dApplication = (class_p3d in classDef.__bases__)
+                    self.beginPackage(name, p3dApplication = p3dApplication)
+                    statements = classDef.__dict__['__statements']
+                    for (lineno, stype, name, args, kw) in statements:
+                        if stype == 'class':
+                            raise PackagerError, 'Nested classes not allowed'
+                        self.__evalFunc(name, args, kw)
+                    package = self.endPackage()
+                    packages.append(package)
+                else:
+                    self.__evalFunc(name, args, kw)
         except PackagerError:
             # Append the line number and file name to the exception
             # error message.
             inst = sys.exc_info()[1]
-            inst.args = (inst.args[0] + ' on line %s of %s' % (self.lineNum, packageDef),)
-            del self.inFile
-            del self.lineNum
+            if not inst.args:
+                inst.args = ('Error',)
+                
+            inst.args = (inst.args[0] + ' on line %s of %s' % (lineno, packageDef),)
             raise
+                    
+        return packages
 
-        del self.inFile
-        del self.lineNum
-
-        packageList = self.packageList
-        self.packageList = []
-
-        return packageList
+    def __evalFunc(self, name, args, kw):
+        """ This is called from readPackageDef(), above, to call the
+        function do_name(*args, **kw), as extracted from the pdef
+        file. """
+        
+        funcname = 'do_%s' % (name)
+        func = getattr(self, funcname)
+        try:
+            func(*args, **kw)
+        except OutsideOfPackageError:
+            message = '%s encountered outside of package definition' % (name)
+            raise OutsideOfPackageError, message
 
     def __expandTabs(self, line, tabWidth = 8):
         """ Expands tab characters in the line to 8 spaces. """
@@ -1430,55 +1496,29 @@ class Packager:
         line = line[:whitespaceCount].lstrip() + line[whitespaceCount:]
         return line
 
-    def parse_set(self, words):
-        """
-        set variable=value
-        """
-        
-        try:
-            command, assign = words
-        except ValueError:
-            raise ArgumentNumber
-        
-        try:
-            variable, value = assign.split('=', 1)
-        except ValueError:
-            raise PackagerError, 'Equals sign required in assignment'
-
-        variable = variable.strip()
-        value = ExecutionEnvironment.expandString(value.strip())
-        ExecutionEnvironment.setEnvironmentVariable(variable, value)
-
-    def parse_host(self, words):
-        """
-        host "url" ["descriptive name"]
-        """
-
-        hostDescriptiveName = None
-        try:
-            if len(words) == 2:
-                command, host = words
-            else:
-                command, host, hostDescriptiveName = words
-        except ValueError:
-            raise ArgumentNumber
+    def do_host(self, hostUrl, descriptiveName = None):
+        """ Specifies the server that will eventually host this
+        published content.  """
 
         if self.currentPackage:
-            self.currentPackage.host = host
+            self.currentPackage.host = hostUrl
         else:
             # Outside of a package, the "host" command specifies the
             # host for all future packages.
-            self.host = host
+            self.host = hostUrl
 
         # The descriptive name, if specified, is kept until the end,
         # where it may be passed to make_contents by ppackage.py.
-        if hostDescriptiveName:
-            self.hostDescriptiveName = hostDescriptiveName
-
-    def parse_model_path(self, words):
-        """
-        model_path directory
-        """
+        if descriptiveName:
+            self.hostDescriptiveName = descriptiveName
+
+    def do_modelPath(self, dirName):
+        """ Specifies an additional directory that will be searched
+        for required models, especially textures.  Models in this
+        directory are not automatically added to the package unless
+        references to them are discovered; use dir() if you want to
+        add an entire directory hierarchy of models.  """
+        
         newName = None
 
         try:
@@ -1488,330 +1528,6 @@ class Packager:
 
         getModelPath().appendDirectory(Filename.fromOsSpecific(dirName))
 
-    def parse_reset_model_path(self, words):
-        """
-        reset_model_path
-        """
-        newName = None
-
-        try:
-            (command,) = words
-        except ValueError:
-            raise ArgumentError
-
-        getModelPath().clear()
-
-    def parse_begin_package(self, words):
-        """
-        begin_package packageName [version=v] [host=host]
-        """
-
-        args = self.__parseArgs(words, ['version', 'host'])
-
-        try:
-            command, packageName = words
-        except ValueError:
-            raise ArgumentNumber
-
-        version = args.get('version', None)
-        host = args.get('host', None)
-
-        self.beginPackage(packageName, version = version, host = host,
-                          p3dApplication = False)
-
-    def parse_end_package(self, words):
-        """
-        end_package packageName
-        """
-
-        try:
-            command, packageName = words
-        except ValueError:
-            raise ArgumentError
-
-        self.endPackage(packageName, p3dApplication = False)
-
-    def parse_begin_p3d(self, words):
-        """
-        begin_p3d appName
-        """
-
-        try:
-            command, packageName = words
-        except ValueError:
-            raise ArgumentNumber
-
-        self.beginPackage(packageName, p3dApplication = True)
-
-    def parse_end_p3d(self, words):
-        """
-        end_p3d appName
-        """
-
-        try:
-            command, packageName = words
-        except ValueError:
-            raise ArgumentError
-
-        self.endPackage(packageName, p3dApplication = True)
-
-    def parse_config(self, words):
-        """
-        config variable=value
-        """
-        
-        try:
-            command, assign = words
-        except ValueError:
-            raise ArgumentNumber
-        
-        try:
-            variable, value = assign.split('=', 1)
-        except ValueError:
-            raise PackagerError, 'Equals sign required in assignment'
-
-        variable = variable.strip()
-        self.config(variable, value)
-
-    def parse_require(self, words):
-        """
-        require packageName [version=v] [host=url]
-        """
-
-        args = self.__parseArgs(words, ['version', 'host'])
-
-        try:
-            command, packageName = words
-        except ValueError:
-            raise ArgumentError
-
-        version = args.get('version', None)
-        host = args.get('host', None)
-        self.require(packageName, version = version, host = host)
-
-    def parse_module(self, words):
-        """
-        module moduleName [newName]
-        """
-        newName = None
-
-        try:
-            if len(words) == 2:
-                command, moduleName = words
-            else:
-                command, moduleName, newName = words
-        except ValueError:
-            raise ArgumentError
-
-        self.module(moduleName, newName = newName)
-
-    def parse_exclude_module(self, words):
-        """
-        exclude_module moduleName [forbid=1]
-        """
-        newName = None
-
-        args = self.__parseArgs(words, ['forbid'])
-
-        try:
-            command, moduleName = words
-        except ValueError:
-            raise ArgumentError
-
-        forbid = args.get('forbid', None)
-        if forbid is not None:
-            forbid = int(forbid)
-        self.excludeModule(moduleName, forbid = forbid)
-
-    def parse_main_module(self, words):
-        """
-        main_module moduleName [newName]
-        """
-        newName = None
-
-        try:
-            if len(words) == 2:
-                command, moduleName = words
-            else:
-                command, moduleName, newName = words
-        except ValueError:
-            raise ArgumentError
-
-        self.mainModule(moduleName, newName = newName)
-
-    def parse_freeze_exe(self, words):
-        """
-        freeze_exe path/to/basename
-        """
-
-        try:
-            command, filename = words
-        except ValueError:
-            raise ArgumentError
-
-        self.freeze(filename, compileToExe = True)
-
-    def parse_freeze_dll(self, words):
-        """
-        freeze_dll path/to/basename
-        """
-
-        try:
-            command, filename = words
-        except ValueError:
-            raise ArgumentError
-
-        self.freeze(filename, compileToExe = False)
-
-    def parse_file(self, words):
-        """
-        file filename [newNameOrDir] [extract=1] [executable=1] [literal=1]
-        """
-
-        args = self.__parseArgs(words, ['extract', 'executable', 'literal'])
-
-        newNameOrDir = None
-
-        try:
-            if len(words) == 2:
-                command, filename = words
-            else:
-                command, filename, newNameOrDir = words
-        except ValueError:
-            raise ArgumentError
-
-        extract = args.get('extract', None)
-        if extract is not None:
-            extract = int(extract)
-
-        executable = args.get('executable', None)
-        if executable is not None:
-            executable = int(executable)
-
-        literal = args.get('literal', None)
-        if literal is not None:
-            literal = int(literal)
-
-        self.file(Filename.fromOsSpecific(filename),
-                  newNameOrDir = newNameOrDir, extract = extract,
-                  executable = executable, literal = literal)
-
-    def parse_inline_file(self, words):
-        """
-        inline_file newName [extract=1] <<[-] eof-symbol
-           .... file text
-           .... file text
-        eof-symbol
-        """
-
-        if not self.currentPackage:
-            raise OutsideOfPackageError
-
-        # Look for the eof-symbol at the end of the command line.
-        eofSymbols = None
-        for i in range(len(words)):
-            if words[i].startswith('<<'):
-                eofSymbols = words[i:]
-                words = words[:i]
-                break
-        if eofSymbols is None:
-            raise PackagerError, 'No << appearing on inline_file'
-
-        # Following the bash convention, <<- means to strip leading
-        # whitespace, << means to keep it.
-        stripWhitespace = False
-        if eofSymbols[0][0:3] == '<<-':
-            stripWhitespace = True
-            eofSymbols[0] = eofSymbols[0][3:]
-        else:
-            eofSymbols[0] = eofSymbols[0][2:]
-
-        # If there's nothing left in the first word, look to the next
-        # word.
-        if not eofSymbols[0]:
-            del eofSymbols[0]
-            
-        if len(eofSymbols) == 1:
-            # The eof symbol is the only word.
-            eofSymbol = eofSymbols[0]
-        else:
-            # It must be only one word.
-            raise PackagerError, 'Only one word may follow <<'
-
-        # Now parse the remaining args.
-        args = self.__parseArgs(words, ['extract'])
-
-        newName = None
-        try:
-            command, newName = words
-        except ValueError:
-            raise ArgumentError
-
-        extract = args.get('extract', None)
-        if extract is not None:
-            extract = int(extract)
-
-        tempFile = Filename.temporary('', self.currentPackage.packageName)
-        temp = open(tempFile.toOsSpecific(), 'w')
-
-        # Now read text from the input file until we come across
-        # eofSymbol on a line by itself.
-        lineNum = self.lineNum
-        line = self.inFile.readline()
-        if not line:
-            raise PackagerError, 'No text following inline_file'
-        lineNum += 1
-        line = line.rstrip()
-        if stripWhitespace:
-            # For the first line, we count up the amount of
-            # whitespace.
-            whitespaceCount = self.__countLeadingWhitespace(line)
-            line = self.__stripLeadingWhitespace(line, whitespaceCount)
-            
-        while line != eofSymbol:
-            print >> temp, line
-            line = self.inFile.readline()
-            if not line:
-                raise PackagerError, 'EOF indicator not found following inline_file'
-            lineNum += 1
-            line = line.rstrip()
-            if stripWhitespace:
-                line = self.__stripLeadingWhitespace(line, whitespaceCount)
-
-        temp.close()
-        self.lineNum = lineNum
-        self.file(tempFile, deleteTemp = True,
-                  newNameOrDir = newName, extract = extract)
-
-    def parse_exclude(self, words):
-        """
-        exclude filename
-        """
-
-        try:
-            command, filename = words
-        except ValueError:
-            raise ArgumentError
-
-        self.exclude(Filename.fromOsSpecific(filename))
-
-    def parse_dir(self, words):
-        """
-        dir dirname [newDir]
-        """
-
-        newDir = None
-
-        try:
-            if len(words) == 2:
-                command, dirname = words
-            else:
-                command, dirname, newDir = words
-        except ValueError:
-            raise ArgumentError
-
-        self.dir(Filename.fromOsSpecific(dirname), newDir = newDir)
-
     def __parseArgs(self, words, argList):
         args = {}
         
@@ -1835,68 +1551,26 @@ class Packager:
             del words[-1]
                 
     
-    def beginPackage(self, packageName, version = None, host = None,
-                     p3dApplication = False):
+    def beginPackage(self, packageName, p3dApplication = False):
         """ Begins a new package specification.  packageName is the
         basename of the package.  Follow this with a number of calls
         to file() etc., and close the package with endPackage(). """
 
         if self.currentPackage:
-            raise PackagerError, 'unmatched end_package %s' % (self.currentPackage.packageName)
-
-        if host is None and not p3dApplication:
-            # Every package that doesn't specify otherwise uses the
-            # current download host.
-            host = self.host
-
-        # A special case when building the "panda3d" package.  We
-        # enforce that the version number matches what we've been
-        # compiled with.
-        if packageName == 'panda3d':
-            if version is None:
-                version = PandaSystem.getPackageVersionString()
-            if host is None:
-                host = PandaSystem.getPackageHostUrl()
-
-            if version != PandaSystem.getPackageVersionString():
-                message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (version, PandaSystem.getPackageVersionString())
-                raise PackageError, message
-
-            if host != PandaSystem.getPackageHostUrl():
-                message = 'mismatched Panda3D host: requested %s, but Panda3D is built as %s' % (host, PandaSystem.getPackageHostUrl())
-                raise PackageError, message
+            raise PackagerError, 'unclosed endPackage %s' % (self.currentPackage.packageName)
 
         package = self.Package(packageName, self)
         self.currentPackage = package
 
-        package.version = version
-        package.host = host
         package.p3dApplication = p3dApplication
-
-        if package.p3dApplication:
-            # Default compression level for an app.
-            package.compressionLevel = 6
-
-            # Every p3dapp requires panda3d.
-            self.require('panda3d')
-            
         package.dryRun = self.dryRun
         
-    def endPackage(self, packageName, p3dApplication = False):
-        """ Closes a package specification.  This actually generates
-        the package file.  The packageName must match the previous
-        call to beginPackage(). """
+    def endPackage(self):
+        """ Closes the current package specification.  This actually
+        generates the package file.  Returns the finished package."""
         
         if not self.currentPackage:
-            raise PackagerError, 'unmatched end_package %s' % (packageName)
-        if self.currentPackage.packageName != packageName:
-            raise PackagerError, 'end_package %s where %s expected' % (
-                packageName, self.currentPackage.packageName)
-        if self.currentPackage.p3dApplication != p3dApplication:
-            if p3dApplication:
-                raise PackagerError, 'end_p3d where end_package expected'
-            else:
-                raise PackagerError, 'end_package where end_p3d expected'
+            raise PackagerError, 'unmatched endPackage'
 
         package = self.currentPackage
         package.close()
@@ -1905,6 +1579,8 @@ class Packager:
         self.packages[(package.packageName, package.platform, package.version)] = package
         self.currentPackage = None
 
+        return package
+
     def findPackage(self, packageName, platform = None, version = None,
                     host = None, requires = None):
         """ Searches for the named package from a previous publish
@@ -2103,18 +1779,20 @@ class Packager:
 
         return None
 
-    def config(self, variable, value):
-        """ Sets the indicated p3d config variable to the given value.
-        This will be written into the p3d_info.xml file at the top of
-        the application, or to the package desc file for a package
-        file. """
+    def do_config(self, **kw):
+        """ Called with any number of keyword parameters.  For each
+        keyword parameter, sets the corresponding p3d config variable
+        to the given value.  This will be written into the
+        p3d_info.xml file at the top of the application, or to the
+        package desc file for a package file. """
 
         if not self.currentPackage:
             raise OutsideOfPackageError
 
-        self.currentPackage.configs[variable] = value
+        for keyword, value in kw.items():
+            self.currentPackage.configs[keyword] = value
 
-    def require(self, packageName, version = None, host = None):
+    def do_require(self, packageName, version = None, host = None):
         """ Indicates a dependency on the named package, supplied as
         a name.
 
@@ -2164,7 +1842,7 @@ class Packager:
 
         self.currentPackage.requirePackage(package)
 
-    def module(self, moduleName, newName = None):
+    def do_module(self, moduleName, newName = None):
         """ Adds the indicated Python module to the current package. """
 
         if not self.currentPackage:
@@ -2172,7 +1850,7 @@ class Packager:
 
         self.currentPackage.freezer.addModule(moduleName, newName = newName)
 
-    def excludeModule(self, moduleName, forbid = False):
+    def do_excludeModule(self, moduleName, forbid = False):
         """ Marks the indicated Python module as not to be included. """
 
         if not self.currentPackage:
@@ -2180,7 +1858,7 @@ class Packager:
 
         self.currentPackage.freezer.excludeModule(moduleName, forbid = forbid)
 
-    def mainModule(self, moduleName, newName = None, filename = None):
+    def do_mainModule(self, moduleName, newName = None, filename = None):
         """ Names the indicated module as the "main" module of the
         application or exe. """
 
@@ -2188,7 +1866,7 @@ class Packager:
             raise OutsideOfPackageError
 
         if self.currentPackage.mainModule and self.currentPackage.mainModule[0] != moduleName:
-            self.notify.warning("Replacing main_module %s with %s" % (
+            self.notify.warning("Replacing mainModule %s with %s" % (
                 self.currentPackage.mainModule[0], moduleName))
 
         if not newName:
@@ -2203,7 +1881,7 @@ class Packager:
 
         self.currentPackage.mainModule = (moduleName, newName)
 
-    def freeze(self, filename, compileToExe = False):
+    def do_freeze(self, filename, compileToExe = False):
         """ Freezes all of the current Python code into either an
         executable (if compileToExe is true) or a dynamic library (if
         it is false).  The resulting compiled binary is added to the
@@ -2256,9 +1934,9 @@ class Packager:
         freezer.reset()
         package.mainModule = None
 
-    def file(self, filename, source = None, newNameOrDir = None,
-             extract = None, executable = None, deleteTemp = False,
-             literal = False):
+    def do_file(self, filename, text = None, newNameOrDir = None,
+                extract = None, executable = None, deleteTemp = False,
+                literal = False):
         """ Adds the indicated arbitrary file to the current package.
 
         The file is placed in the named directory, or the toplevel
@@ -2279,6 +1957,10 @@ class Packager:
         newNameOrDir is unspecified or None, the file is placed in the
         toplevel directory, regardless of its source directory.
 
+        If text is nonempty, it contains the text of the file.  In
+        this case, the filename is not read, but the supplied text is
+        used instead.
+
         If extract is true, the file is explicitly extracted at
         runtime.
 
@@ -2334,6 +2016,17 @@ class Packager:
                     message = 'Cannot install multiple files on target filename %s' % (newName)
                     raise PackagerError, message
 
+        if text:
+            if not newName:
+                newName = filename.cStr()
+
+            tempFile = Filename.temporary('', self.currentPackage.packageName)
+            temp = open(tempFile.toOsSpecific(), 'w')
+            temp.write(text)
+            temp.close()
+            files = [tempFile.toOsSpecific()]
+            deleteTemp = True
+
         for filename in files:
             filename = Filename.fromOsSpecific(filename)
             basename = filename.getBasename()
@@ -2346,7 +2039,7 @@ class Packager:
                 explicit = explicit, executable = executable,
                 deleteTemp = deleteTemp)
 
-    def exclude(self, filename):
+    def do_exclude(self, filename):
         """ Marks the indicated filename as not to be included.  The
         filename may include shell globbing characters, and may or may
         not include a dirname.  (If it does not include a dirname, it
@@ -2358,7 +2051,7 @@ class Packager:
 
         self.currentPackage.excludeFile(filename)
 
-    def dir(self, dirname, newDir = None):
+    def do_dir(self, dirname, newDir = None):
 
         """ Adds the indicated directory hierarchy to the current
         package.  The directory hierarchy is walked recursively, and
@@ -2407,3 +2100,83 @@ class Packager:
             if ext in self.knownExtensions:
                 self.currentPackage.addFile(filename, newName = newName,
                                             explicit = False)
+
+
+# The following class and function definitions represent a few sneaky
+# Python tricks to allow the pdef syntax to contain the pseudo-Python
+# code they do.  These tricks bind the function and class definitions
+# into a bit table as they are parsed from the pdef file, so we can
+# walk through that table later and perform the operations requested
+# in order.
+
+class metaclass_def(type):
+    """ A metaclass is invoked by Python when the class definition is
+    read, for instance to define a child class.  By defining a
+    metaclass for class_p3d and class_package, we can get a callback
+    when we encounter "class foo(p3d)" in the pdef file.  The callback
+    actually happens after all of the code within the class scope has
+    been parsed first. """
+    
+    def __new__(self, name, bases, dict):
+
+        # At the point of the callback, now, "name" is the name of the
+        # class we are instantiating, "bases" is the list of parent
+        # classes, and "dict" is the class dictionary we have just
+        # parsed.
+
+        # If "dict" contains __metaclass__, then we must be parsing
+        # class_p3d or class_ppackage, below--skip it.  But if it
+        # doesn't contain __metaclass__, then we must be parsing
+        # "class foo(p3d)" (or whatever) from the pdef file.
+        
+        if '__metaclass__' not in dict:
+            # Get the context in which this class was created
+            # (presumably, the module scope) out of the stack frame.
+            frame = sys._getframe(1)
+            mdict = frame.f_locals
+            lineno = frame.f_lineno
+
+            # Store the class name on a statements list in that
+            # context, so we can later resolve the class names in
+            # the order they appeared in the file.
+            mdict.setdefault('__statements', []).append((lineno, 'class', name, None, None))
+            
+        return type.__new__(self, name, bases, dict)
+
+class class_p3d:
+    __metaclass__ = metaclass_def
+    pass
+
+class class_package:
+    __metaclass__ = metaclass_def
+    pass
+
+class func_closure:
+
+    """ This class is used to create a closure on the function name,
+    and also allows the *args, **kw syntax.  In Python, the lambda
+    syntax, used with default parameters, is used more often to create
+    a closure (that is, a binding of one or more variables into a
+    callable object), but that syntax doesn't work with **kw.
+    Fortunately, a class method is also a form of a closure, because
+    it binds self; and this doesn't have any syntax problems with
+    **kw. """
+
+    def __init__(self, name):
+        self.name = name
+
+    def generic_func(self, *args, **kw):
+        """ This method is bound to all the functions that might be
+        called from the pdef file.  It's a special function; when it is
+        called, it does nothing but store its name and arguments in the
+        caller's local scope, where they can be pulled out later. """
+
+        # Get the context in which this function was called (presumably,
+        # the class dictionary) out of the stack frame.
+        frame = sys._getframe(1)
+        cldict = frame.f_locals
+        lineno = frame.f_lineno
+
+        # Store the function on a statements list in that context, so we
+        # can later walk through the function calls for each class.
+        cldict.setdefault('__statements', []).append((lineno, 'func', self.name, args, kw))

+ 5 - 4
direct/src/p3d/packp3d.py

@@ -118,12 +118,13 @@ def makePackedApp(args):
         packager.setup()
         packager.beginPackage(appBase, p3dApplication = True)
         for requireName in requires:
-            packager.require(requireName)
+            packager.do_require(requireName)
 
-        packager.dir(root)
-        packager.mainModule(mainModule)
+        packager.do_dir(root)
+        packager.do_mainModule(mainModule)
 
-        packager.endPackage(appBase, p3dApplication = True)
+        packager.endPackage()
+        
     except Packager.PackagerError:
         # Just print the error message and exit gracefully.
         inst = sys.exc_info()[1]

+ 147 - 154
direct/src/p3d/panda3d.pdef

@@ -13,157 +13,150 @@
 # not be needed by any one particular application.
 
 
-begin_package panda3d
-
-# The core Panda3D package.  Contains Python and most of the graphics
-# code in Panda3D.
-
-config display_name="Panda3D"
-
-# This is the key Python module that is imported at runtime to start
-# an application running.
-module direct.p3d.AppRunner
-
-# These are additional Python modules that are needed by most Panda3D
-# applications.  It doesn't matter too much if we miss one or two
-# here, since any module imported by any of this code will
-# automatically be included as well, and we end up with a pretty
-# complete list that way.
-module direct.directbase.DirectStart
-module direct.showbase.*
-module direct.actor.Actor
-module direct.fsm.FSM
-module direct.interval.IntervalGlobal
-module direct.particles.ParticleEffect
-module direct.directutil.Mopath
-
-# Exclude these GUI toolkits; they're big, and many applications don't
-# use them.  We define them as separate, optional packages, below.
-exclude_module wx
-exclude_module direct.showbase.WxGlobal
-
-exclude_module Tkinter
-exclude_module direct.showbase.TkGlobal
-exclude_module direct.tkpanels
-exclude_module direct.tkwidgets
-
-# Bind all of the above Python code into a frozen DLL.  This makes the
-# Python code available when the DLL is imported.  It is actually
-# preferable not to use freeze, but instead just to leave the Python
-# code directly within the Multifile; but in this case we have to use
-# freeze on this very first package, due to bootstrapping
-# requirements.  (Part of the code we're including here is the code
-# required to load Python code from a Multifile, so it can't be placed
-# within a Multifile itself.)
-freeze_dll runp3d_frozen
-
-# This is the main program that drives the plugin application.  It is
-# responsible for loading runp3d_frozen, above, and then importing
-# direct.p3d.runp3d, to start an application running.  Note that
-# the .exe extension is automatically replaced with the
-# platform-specific extension appropriate for an executable.
-file p3dpython.exe
-
-# Most of the core Panda3D DLL's will be included implicitly due to
-# being referenced by the above Python code.  Here we name a few more
-# that are also needed, but aren't referenced by any code.  Again,
-# note that the .dll extension is automatically replaced with the
-# platform-specific extension for an executable.
-file libpandagl.dll
-file libpandadx8.dll
-file libpandadx9.dll
-file libtinydisplay.dll
-
-# A basic config file is needed to lay some some fundamental runtime
-# variables.
-inline_file Config.prc extract=1 <<- <EOF>
-    plugin-path $PANDA3D_ROOT
-
-    aux-display pandagl
-    aux-display pandadx9
-    aux-display pandadx8
-    aux-display tinydisplay
-
-    default-model-extension .bam
-<EOF>
-
-end_package panda3d
-
-
-begin_package egg
-
-# This package contains the code for reading and operating on egg
-# files.  Since the Packager automatically converts egg files to bam
-# files, this is not needed for most Panda3D applications.
-
-config display_name="Panda3D egg loader"
-require panda3d
-
-file libpandaegg.dll
-
-inline_file egg.prc extract=1 <<- <EOF>
-    plugin-path $EGG_ROOT
-    load-file-type egg pandaegg
-<EOF>
-
-end_package egg
-
-
-begin_package wx
-config display_name="wxPython GUI Toolkit"
-require panda3d
-
-module direct.showbase.WxGlobal
-module wx
-module wx.*
-
-end_package wx
-
-
-
-begin_package tk
-config display_name="Tk GUI Toolkit"
-require panda3d
-
-module Tkinter
-module direct.showbase.TkGlobal
-module direct.tkpanels
-module direct.tkwidgets
-
-end_package tk
-
-
-begin_p3d packp3d
-
-# This application is a command-line convenience for building a p3d
-# application out of a directory hierarchy on disk.  We build it here
-# into its own p3d application, to allow end-users to easily build p3d
-# applications using the appropriate version of Python and Panda for
-# the targeted runtime.
-
-config display_name="Panda3D Application Packer"
-config full_disk_access=1
-config hidden=1
-require panda3d
-require egg
-
-main_module direct.p3d.packp3d
-
-end_p3d packp3d
-
-
-begin_p3d ppackage
-
-# As above, a packaging utility.  This is the fully-general ppackage
-# utility, which reads pdef files (like this one!) and creates one or
-# more packages or p3d applications.
-
-config display_name="Panda3D General Package Utility"
-config full_disk_access=1
-config hidden=1
-require panda3d
-require egg
-
-main_module direct.p3d.ppackage
-
-end_p3d ppackage
+class panda3d(package):
+
+    # The core Panda3D package.  Contains Python and most of the graphics
+    # code in Panda3D.
+
+    config(display_name = "Panda3D")
+
+    # This is the key Python module('that is imported at runtime to start')
+    # an application running.
+    module('direct.p3d.AppRunner')
+
+    # These are additional Python modules that are needed by most Panda3D
+    # applications.  It doesn't matter too much if we miss one or two
+    # here, since any module('imported by any of this code will')
+    # automatically be included as well, and we end up with a pretty
+    # complete list that way.
+    module('direct.directbase.DirectStart')
+    module('direct.showbase.*')
+    module('direct.actor.Actor')
+    module('direct.fsm.FSM')
+    module('direct.interval.IntervalGlobal')
+    module('direct.particles.ParticleEffect')
+    module('direct.directutil.Mopath')
+
+    # Exclude these GUI toolkits; they're big, and many applications don't
+    # use them.  We define them as separate, optional packages, below.
+    excludeModule('wx')
+    excludeModule('direct.showbase.WxGlobal')
+
+    excludeModule('Tkinter')
+    excludeModule('direct.showbase.TkGlobal')
+    excludeModule('direct.tkpanels')
+    excludeModule('direct.tkwidgets')
+
+    # Bind all of the above Python code into a frozen DLL.  This makes the
+    # Python code available when the DLL is imported.  It is actually
+    # preferable not to use freeze, but instead just to leave the Python
+    # code directly within the Multifile; but in this case we have to use
+    # freeze on this very first package, due to bootstrapping
+    # requirements.  (Part of the code we're including here is the code
+    # required to load Python code from a Multifile, so it can't be placed
+    # within a Multifile itself.)
+    freeze('runp3d_frozen', compileToExe = False)
+
+    # This is the main program that drives the plugin application.  It is
+    # responsible for loading runp3d_frozen, above, and then importing
+    # direct.p3d.runp3d, to start an application running.  Note that
+    # the .exe extension is automatically replaced with the
+    # platform-specific extension appropriate for an executable.
+    file('p3dpython.exe')
+
+    # Most of the core Panda3D DLL's will be included implicitly due to
+    # being referenced by the above Python code.  Here we name a few more
+    # that are also needed, but aren't referenced by any code.  Again,
+    # note that the .dll extension is automatically replaced with the
+    # platform-specific extension for an executable.
+    file('libpandagl.dll')
+    if platform.startswith('win'):
+        file('libpandadx8.dll')
+        file('libpandadx9.dll')
+    file('libtinydisplay.dll')
+
+    # A basic config file is needed to lay some some fundamental runtime
+    # variables.
+    if platform.startswith('win'):
+        auxDisplays = """
+aux-display pandagl
+aux-display pandadx9
+aux-display pandadx8
+aux-display tinydisplay
+"""
+    else:
+        auxDisplays = """
+aux-display pandagl
+aux-display tinydisplay
+"""
+        
+    file('Config.prc', extract = True, text = """
+plugin-path $PANDA3D_ROOT
+default-model-extension .bam
+""" + auxDisplays)
+
+
+class egg(package):
+
+    # This package contains the code for reading and operating on egg
+    # files.  Since the Packager automatically converts egg files to bam
+    # files, this is not needed for most Panda3D applications.
+
+    config(display_name = "Panda3D egg loader")
+    require('panda3d')
+
+    file('libpandaegg.dll')
+
+    file('egg.prc', extract = True, text = """
+plugin-path $EGG_ROOT
+load-file-type egg pandaegg
+""")
+
+class wx(package):
+    config(display_name = "wxPython GUI Toolkit")
+    require('panda3d')
+
+    module('direct.showbase.WxGlobal')
+    module('wx')
+    module('wx.*')
+
+
+class tk(package):
+    config(display_name = "Tk GUI Toolkit")
+    require('panda3d')
+
+    module('Tkinter')
+    module('direct.showbase.TkGlobal')
+    module('direct.tkpanels')
+    module('direct.tkwidgets')
+
+class packp3d(p3d):
+
+    # This application is a command-line convenience for building a p3d
+    # application out of a directory hierarchy on disk.  We build it here
+    # into its own p3d application, to allow end-users to easily build p3d
+    # applications using the appropriate version of Python and Panda for
+    # the targeted runtime.
+
+    config(display_name = "Panda3D Application Packer")
+    config(full_disk_access = True)
+    config(hidden = True)
+    require('panda3d')
+    require('egg')
+
+    mainModule('direct.p3d.packp3d')
+
+
+class ppackage(p3d):
+
+    # As above, a packaging utility.  This is the fully-general ppackage
+    # utility, which reads pdef files (like this one!) and creates one or
+    # more packages or p3d applications.
+
+    config(display_name = "Panda3D General Package Utility")
+    config(full_disk_access = True)
+    config(hidden = True)
+    require('panda3d')
+    require('egg')
+
+    mainModule('direct.p3d.ppackage')

+ 1 - 0
direct/src/p3d/ppackage.py

@@ -144,6 +144,7 @@ except Packager.PackagerError:
     # Just print the error message and exit gracefully.
     inst = sys.exc_info()[1]
     print inst.args[0]
+    #raise
     sys.exit(1)
 
 # Look to see if we built any true packages, or if all of them were