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
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
0047
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')
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
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
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