0001"""Kid Compiler
0002
0003Compile XML to Python byte-code.
0004"""
0005
0006from __future__ import generators
0007
0008__revision__ = "$Rev: 262 $"
0009__date__ = "$Date: 2006-01-28 16:38:09 +0000 (Sat, 28 Jan 2006) $"
0010__author__ = "Ryan Tomayko (rtomayko@gmail.com)"
0011__copyright__ = "Copyright 2004-2005, Ryan Tomayko"
0012__license__ = "MIT <http://www.opensource.org/licenses/mit-license.php>"
0013
0014import sys
0015import re
0016import os
0017import os.path
0018import imp
0019import stat
0020import struct
0021import marshal
0022import new
0023
0024import kid.parser
0025
0026__all__ = ['KID_EXT', 'compile', 'compile_file', 'compile_dir']
0027
0028# kid filename extension
0029KID_EXT = ".kid"
0030
0031py_compile = compile
0032
0033def actualize(code, dict=None):
0034    if dict is None:
0035        dict = {}
0036    exec code in dict
0037    return dict
0038
0039def compile(source, filename='<string>', encoding=None):
0040    """Compiles kid xml source to a Python code object.
0041
0042    source   -- A file like object - must support read.
0043    filename -- An optional filename that is used
0044
0045    """
0046    # XXX all kinds of work to do here catching syntax errors and
0047    #     adjusting line numbers..
0048    py = kid.parser.parse(source, encoding, filename=filename)
0049    return py_compile(py, filename, 'exec')
0050
0051_timestamp = lambda filename : os.stat(filename)[stat.ST_MTIME]
0052
0053class KidFile(object):
0054    magic = imp.get_magic()
0055
0056    def __init__(self, kid_file, force=0, encoding=None, strip_dest_dir=None):
0057        self.kid_file = kid_file
0058        self.py_file = os.path.splitext(kid_file)[0] + '.py'
0059        self.strip_dest_dir = strip_dest_dir
0060        self.pyc_file = self.py_file + 'c'
0061        self.encoding = encoding or 'utf-8'
0062        fp = None
0063        if force:
0064            stale = 1
0065        else:
0066            stale = 0
0067            try:
0068                fp = open(self.pyc_file, "rb")
0069            except IOError:
0070                stale = 1
0071            else:
0072                if fp.read(4) != self.magic:
0073                    stale = 1
0074                else:
0075                    mtime = struct.unpack('<I', fp.read(4))[0]
0076                    kid_mtime = _timestamp(kid_file)
0077                    if kid_mtime is None or mtime < kid_mtime:
0078                        stale = 1
0079        self.stale = stale
0080        self._pyc_fp = fp
0081        self._python = None
0082        self._code = None
0083
0084    def compile(self, dump_code=1, dump_source=0):
0085        if dump_source:
0086            self.dump_source()
0087        code = self.code
0088        if dump_code and self.stale:
0089            self.dump_code()
0090        return code
0091
0092    def code(self):
0093        if self._code is None:
0094            if not self.stale:
0095                self._code = marshal.load(self._pyc_fp)
0096            else:
0097                pyfile = self.py_file
0098                if self.strip_dest_dir and                      self.py_file.startswith(self.strip_dest_dir):
0100                    pyfile = os.path.normpath(self.py_file[len(self.strip_dest_dir):])
0101                self._code = py_compile(self.python, pyfile, 'exec')
0102        return self._code
0103    code = property(code)
0104
0105    def python(self):
0106        """Get the Python source for the template."""
0107        if self._python is None:
0108            py = kid.parser.parse_file(self.kid_file, self.encoding)
0109            self._python = py
0110        return self._python
0111    python = property(python)
0112
0113    def dump_source(self, file=None):
0114        py = self.python
0115        file = file or self.py_file
0116        try:
0117            fp = _maybe_open(file, 'wb')
0118            fp.write('# -*- coding: utf-8 -*-\n') # PEP 0263
0119            fp.write(self.python.encode('utf-8'))
0120        except IOError:
0121            try:
0122                os.unlink(file)
0123            except OSError:
0124                pass
0125            return 0
0126        else:
0127            return 1
0128
0129    def dump_code(self, file=None):
0130        code = self.code
0131        file = file or self.pyc_file
0132        try:
0133            fp = _maybe_open(file, 'wb')
0134            if self.kid_file:
0135                mtime = os.stat(self.kid_file)[stat.ST_MTIME]
0136            else:
0137                mtime = 0
0138            fp.write('\0\0\0\0')
0139            fp.write(struct.pack('<I', mtime))
0140            marshal.dump(code, fp)
0141            fp.flush()
0142            fp.seek(0)
0143            fp.write(self.magic)
0144        except IOError:
0145            try:
0146                os.unlink(file)
0147            except OSError:
0148                pass
0149            return 0
0150        else:
0151            return 1
0152
0153def _maybe_open(f, mode):
0154    if isinstance(f, basestring):
0155        return open(f, mode)
0156    else:
0157        return f
0158
0159#
0160# functions for compiling files directly and the kidc utility
0161#
0162
0163def compile_file(file, force=0, source=0, encoding=None, strip_dest_dir=None):
0164    """Compile the file specified.
0165
0166    Return True if the file was compiled, False if the compiled file already
0167    exists and is up-to-date.
0168
0169    """
0170    template = KidFile(file, force, encoding, strip_dest_dir)
0171    if template.stale:
0172        template.compile(dump_source=source)
0173        return 1
0174    else:
0175        return 0
0176
0177
0178def compile_dir(dir, maxlevels=10, force=0, source=0, encoding=None, strip_dest_dir=None):
0179    """Byte-compile all kid modules in the given directory tree.
0180
0181    Keyword Arguments: (only dir is required)
0182    dir       -- the directory to byte-compile
0183    maxlevels -- maximum recursion level (default 10)
0184    force     -- if true, force compilation, even if timestamps are up-to-date.
0185    source    -- if true, dump python source (.py) files along with .pyc files.
0186
0187    """
0188    names = os.listdir(dir)
0189    names.sort()
0190    success = 1
0191    ext_len = len(KID_EXT)
0192    for name in names:
0193        fullname = os.path.join(dir, name)
0194        if os.path.isfile(fullname):
0195            head, tail = name[:-ext_len], name[-ext_len:]
0196            if tail == KID_EXT:
0197                try:
0198                    rslt = compile_file(fullname, force, source, encoding, strip_dest_dir)
0199                except Exception, e:
0200                    # TODO: grab the traceback and yield it with the other stuff
0201                    rslt = e
0202                yield (rslt, fullname)
0203        elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
0204                    os.path.isdir(fullname) and not os.path.islink(fullname)):
0205            for rslt in compile_dir(fullname, maxlevels - 1, force, source, strip_dest_dir):
0206                yield rslt
0207    return