# -*- coding: iso-8859-1 -*-
#-----------------------------------------------------------------------------
# Modeling Framework: an Object-Relational Bridge for python
#
# Copyright (c) 2001-2004 Sbastien Bigaret <sbigaret@users.sourceforge.net>
# All rights reserved.
#
# This file is part of the Modeling Framework.
#
# This code is distributed under a "3-clause BSD"-style license;
# see the LICENSE file for details.
#-----------------------------------------------------------------------------
"""
ModelMason contains the base class for every ''mason'' and is of no interest
except if you plan to design a new module to generate code/files from a Model.
When this is the case, the class' documentation indicate the rules that should
be respected to ensure easy integration with the framework's tools such as the
script mdl_generate_python_code or the ZModeler.
CVS information
$Id: ModelMason.py 974 2006-02-26 14:06:07Z sbigaret $
"""
__version__='$Revision: 974 $'[11:-2]
import time, os, string, sys
from Cheetah.Template import Template
class ModelMason:
"""
This class should be the base class for every masons.
When subclassing this class, you should take care of:
- call ModelMason.__init__() if it is overridden
- if the subclass needs to change/create sth. on the filesystem, it *must*
check whether 'fake_mode' is set: if it is set, absolutely no changes
should be made on the disk.
Methods createEmptyFile(), createFileFromTemplate(), copyFile() and
build_package() can be used without this precaution since they already
check 'self.fake_mode' before making any changes on the disk.
- call log() to record any action relative to the generation (such as the
creation of a file). Please note that you should be ready to log these
actions even when fake_mode is set. For example, suppose 'file.py'
should be generated but not overwritten; if the file does not exist
you'd log('Creating file file.py'), and if it exists you'd log('File.py
exists, skipping') whether fake_mode is set or not. This makes it
possible for the user to see what would happen whene (re)generating the
code without actually making the changes.
Following these rules makes it easy to integrate a custom ''mason'' into
the script mdl_generate_python_code and the ZModeler.
All subclasses need to override build() and put there the logic which
generates the code. You will probably override method tmpl_namespace() as
well (see its documentation for details).
You can also refer to PyModelMason for an example of use.
"""
def __init__(self, model, rootPath, concreteBuilder, bricksDir,
verbose_mode=0, fake_mode=0):
"""
Initializes the ModelMason so that the built files are based on the
supplied model.
Parameters:
model -- the model from which the generated python package should be
generated
rootPath -- path of a directory where the corresponding package should
be dropped
concreteBuilder -- the module containing the concrete builder
bricksDir -- path for the directory containing the templates, relative
to the path where the module of 'concreteBuilder' is stored
verbose_mode -- whether logging is activated or not, see log(). When
true, the building process logs some informations in sys.stderr while
generating the files
fake_mode -- if true, do not create or change any file, just report what
would be done
Subclasses may decide to supply a default value for the product's base
directory when parameter 'rootPath' is not supplied.
"""
self.model = model
self.rootPath=rootPath
packagePathList=[rootPath]
packagePathList.extend(string.split(self.model.packageName(), '.'))
self.packagePath = apply(os.path.join, packagePathList)
self.bricksPath=os.path.join(os.path.dirname(concreteBuilder.__file__),
bricksDir)
self.verbose_mode=verbose_mode
self.fake_mode=fake_mode
def fullPathForBrick(self, aBrick):
"""
Returns the full path for a given brick's filename.
This is the preferred way for accessing bricks
"""
return os.path.join(self.bricksPath(), aBrick)
def fullPathForGeneratedFile(self, filename):
"Returns the full path for a given generated filename."
return os.path.join(self.packagePath, filename)
def createEmptyFile(self,filename,overwrite=0):
"""
Create the empty file 'filename' ; the filename is a relative path
(relative to 'self.productBaseDirectory()')
"""
if not os.path.isabs(filename):
filename=self.fullPathForGeneratedFile(filename)
if not overwrite and os.path.exists(filename):
self.log('File %s exists, skipping\n'%filename)
return
self.log('Creating empty file %s\n'%filename)
if not self.fake_mode:
f = open(filename,"w")
f.close()
def copyFile(self, templateFilename, destinationFilename,overwrite=0):
"""
Copy the template file to the destination file, unchanged. Both filenames
should be relative to, respectively, bricksBaseDirectory and
productBaseDirectory.
"""
if not os.path.isabs(filename):
filename=self.fullPathForGeneratedFile(filename)
if not overwrite and os.path.exists(filename):
self.log('File %s exists, skipping\n'%filename)
return
self.log('Creating file %s\n'%destinationFilename)
if not self.fake_mode:
_f1 = open(self.fullPathForGeneratedFile(destinationFilename),'w')
_f2 = open(self.fullPathForBrick(templateFilename),'r')
_f1.write(_f2.read())
_f1.close()
_f2.close()
_marker=[]
def templateObjectForTemplate(self, template, namespace=_marker):
"""
Initializes a Template object from the supplied templateFile. Overrides
this to perform any additional initializations for Templates, such as
building a namespace for the template.
Parameters:
template -- a Cheetah.Template object
namespace -- the template will use that namespace. If ommitted, it
defaults to self.tmpl_namespace()
Default implementation simply returns the Template object
"""
if namespace is self._marker: namespace=self.tmpl_namespace()
namespace=self.fix_tmpl_namespace(namespace)
#for dict in namespace:
for key in namespace.keys():
setattr(template, key, namespace[key])
return template
def createFileFromTemplate(self, template, destFile, namespace=_marker,
overwrite=0):
"""
Parameters:
template -- a Cheetah.Template object
namespace -- the template will use that namespace. If ommitted, it
defaults to self.tmpl_namespace()
destFile -- the destination file path, relative to productBaseDirectory
"""
if namespace is self._marker: namespace=self.tmpl_namespace()
namespace=self.fix_tmpl_namespace(namespace)
destFile = self.fullPathForGeneratedFile(destFile)
file_exists=os.path.exists(destFile)
if not overwrite and file_exists:
self.log("File %s exists, skipping\n"%destFile)
return
if not overwrite or (overwrite and not file_exists):
self.log("Generating %s" % destFile)
else:
self.log("Overwriting %s" % destFile)
if not self.fake_mode:
f = open(destFile,'w')
f.write("%s"%self.templateObjectForTemplate(template,namespace=namespace))
self.log("... done")
self.log('\n')
def build(self):
"""
Build the product ; override it to match your need. Base implementation
does nothing.
"""
pass
def fix_tmpl_namespace(self, namespace):
"""
Internally used to make any values in the namespace callable --if a value
is an instance or a python object, it is by a lambda function returning
the value. We make this because Cheetah sometimes requires a callable.
"""
# builtin callable() does not work on instances derived from
# ZODB.Persistent, we use that one instead
def _callable(o):
if hasattr(o,'__class__'): return hasattr(o,'__call__')
return callable(o)
d={}
for k,v in namespace.items():
if _callable(v): d[k]=v
else: d[k]=lambda p=v: p
return d
def tmpl_namespace(self):
"""
This method returns a dictionary used by templates to search for specific
values.
Default implementation returns::
{'model': self.model}
Subclasses override this method to provide their own namespace. This
namespace is the default one transmitted to the Cheetah template when no
specific namespace is passed to method createFileFromTemplate().
"""
return {'model': self.model}
def build_package(self):
"""
Creates all the necessary directories for the package, which can be
something like A.B.C.MyPackage. Creates an empty '__init__.py' in each
sub-directories if needed: existing __init__.py are not overwritten.
"""
currentPath=self.rootPath
packList=[]
for pack in string.split(self.model.packageName(), '.')[:-1]:
currentPath=os.path.join(currentPath, pack)
self.log('Creating directory %s'%currentPath)
if not self.fake_mode:
try:
os.mkdir(currentPath)
except: self.log('... no')
else: self.log('... ok')
self.log('\n')
init=os.path.join(currentPath, '__init__.py')
if os.path.exists(init):
self.log('%s exists, skipping\n'%init)
else:
self.log('Creating %s\n'%init)
if not self.fake_mode:
f=open(init,'w') ; f.close()
# Last, create self.packagePath
self.log('Creating directory %s'%self.packagePath)
if not self.fake_mode:
try:
os.mkdir(self.packagePath)
except: self.log('... no')
else: self.log('... ok')
self.log('\n')
def log(self, msg):
"Logs the msg to stderr if self.verbose_mode is true"
if self.verbose_mode:
sys.stderr.write(msg)
def entitiesSet(self):
"""
Returns a list of list of entities, where Entities in the same list share
the same 'moduleName'
"""
d={}
for entity in self.model.entities():
moduleName=entity.moduleName()
if d.get(moduleName):
d[moduleName].append(entity)
else: d[moduleName]=[entity]
return d.values()
|