""" Cobbler provides builtin methods for use in Cheetah templates. $SNIPPET is one such function and is now used to implement Cobbler's SNIPPET:: syntax. Written by Daniel Guernsey Contributions by Michael DeHaan This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """ import Cheetah.Template import os.path import re CHEETAH_MACROS_FILE = '/etc/cobbler/cheetah_macros' # This class is defined using the Cheetah language. Using the 'compile' function # we can compile the source directly into a python class. This class will allow # us to define the cheetah builtins. BuiltinTemplate = Cheetah.Template.Template.compile(source="\n".join([ # This part (see 'Template' below # for the other part) handles the actual inclusion of the file contents. We # still need to make the snippet's namespace (searchList) available to the # template calling SNIPPET (done in the other part). # Moved the other functions into /etc/cobbler/cheetah_macros # Left SNIPPET here since it is very important. # This function can be used in two ways: # Cheetah syntax: # # $SNIPPET('my_snippet') # # SNIPPET syntax: # # SNIPPET::my_snippet # # This follows all of the rules of snippets and advanced snippets. First it # searches for a per-system snippet, then a per-profile snippet, then a # general snippet. If none is found, a comment explaining the error is # substituted. "#def SNIPPET($file)", "#set $fullpath = $find_snippet($file)", "#if $fullpath", "#include $fullpath", "#else", "# Error: no snippet data for $file", "#end if", "#end def", ]) + "\n") MacrosTemplate = Cheetah.Template.Template.compile(file=CHEETAH_MACROS_FILE) class Template(BuiltinTemplate, MacrosTemplate): """ This class will allow us to include any pure python builtin functions. It derives from the cheetah-compiled class above. This way, we can include both types (cheetah and pure python) of builtins in the same base template. We don't need to override __init__ """ # OK, so this function gets called by Cheetah.Template.Template.__init__ to # compile the template into a class. This is probably a kludge, but it # add a baseclass argument to the standard compile (see Cheetah's compile # docstring) and returns the resulting class. This argument, of course, # points to this class. Now any methods entered here (or in the base class # above) will be accessible to all cheetah templates compiled by cobbler. def compile(klass, *args, **kwargs): """ Compile a cheetah template with cobbler modifications. Modifications include SNIPPET:: syntax replacement and inclusion of cobbler builtin methods. """ def replacer(match): return "$SNIPPET('%s')" % match.group(1) def preprocess(source, file): # Normally, the cheetah compiler worries about this, but we need to # preprocess the actual source if source is None: if isinstance(file, (str, unicode)): if os.path.exists(file): f = open(file) source = "#errorCatcher Echo\n" + f.read() f.close() else: source = "# Unable to read %s\n" % file elif hasattr(file, 'read'): source = file.read() file = None # Stop Cheetah from throwing a fit. rx = re.compile(r'SNIPPET::([A-Za-z0-9_\-\/\.]+)') results = rx.sub(replacer, source) return (results, file) preprocessors = [preprocess] if kwargs.has_key('preprocessors'): preprocessors.extend(kwargs['preprocessors']) kwargs['preprocessors'] = preprocessors # Instruct Cheetah to use this class as the base for all cheetah templates if not kwargs.has_key('baseclass'): kwargs['baseclass'] = Template # Now let Cheetah do the actual compilation return Cheetah.Template.Template.compile(*args, **kwargs) compile = classmethod(compile) def find_snippet(self, file): """ Locate the appropriate snippet for the current system and profile. This will first check for a per-system snippet, a per-profile snippet, and a general snippet. If no snippet is located, it returns None. """ if self.varExists('system_name'): fullpath = '%s/per_system/%s/%s' % (self.getVar('snippetsdir'), file, self.getVar('system_name')) if os.path.exists(fullpath): return fullpath if self.varExists('profile_name'): fullpath = '%s/per_profile/%s/%s' % (self.getVar('snippetsdir'), file, self.getVar('profile_name')) if os.path.exists(fullpath): return fullpath return '%s/%s' % (self.getVar('snippetsdir'), file) # This may be a little frobby, but it's really cool. This is a pure python # portion of SNIPPET that appends the snippet's searchList to the caller's # searchList. This makes any #defs within a given snippet available to the # template that included the snippet. def SNIPPET(self, file): """ Include the contents of the named snippet here. This is equivalent to the #include directive in Cheetah, except that it searches for system and profile specific snippets, and it includes the snippet's namespace. """ # First, do the actual inclusion. Cheetah (when processing #include) # will track the inclusion in self._CHEETAH__cheetahIncludes result = BuiltinTemplate.SNIPPET(self, file) # Now do our dirty work: locate the new include, and append its # searchList to ours. # We have to compute the full path again? Eww. fullpath = self.find_snippet(file); if fullpath: # Only include what we don't already have. Because Cheetah # passes our searchList into included templates, the snippet's # searchList will include this templates searchList. We need to # avoid duplicating entries. childList = self._CHEETAH__cheetahIncludes[fullpath].searchList() myList = self.searchList() for childElem in childList: if not childElem in myList: myList.append(childElem) return result # This function is used by several cheetah methods in cheetah_macros. # It can be used by the end user as well. # Ex: Replace all instances of '/etc/banner' with a value stored in # $new_banner # # sed 's/$sedesc("/etc/banner")/$sedesc($new_banner)/' # def sedesc(self, value): """ Escape a string for use in sed. """ def escchar(c): if c in '/^.[]$()|*+?{}\\': return '\\' + c else: return c return ''.join([escchar(c) for c in value])