#!/usr/bin/env python """ Use templates + yaml to generate files. pork.py is a simple file generator which combines yaml and a templating engine in a very stripped down way. Usage is simple: ./pork.py [ ...] The YAML file should contain 2 YAML documents. The first document is config information, and the second a structure which corresponds with the variables referenced in your template(s). The config document must contain one key: template -- full path to a template file on disk By default pork.py sends the rendered results to standard output. However, if the config document contains the optional ``target`` item, the value is used as the file to write on disk: target -- full path to the target file (the file you are generating) A third key the config document recognises is ``engine``. This is the name of the template engine you want to use. The default is django; other supported values are jinja2, mako, and python (string.Template interpolation). engine -- name of the template engine to use (default 'django') NB if you use django, no settings module is required, or used. If your template refers to other templates (eg a django {% include %}), those others must be in the same directory as the original. The second document is loaded into a python dictionary and passed to the chosen engine's rendering method (a dictionary, kwargs, etc). pork.py only really does sanity checking on its own input. Any errors caused by bad YAML, bad filenames, bad templates, etc, are dealt with by python, yaml, or the engine itself. See the YAML spec or the PyYAML docs for how to write YAML documents and files. Here's a super-trivial example: hello.yml: --- template: hello.html --- hello: world hello.html: Hello {{hello}} $ ./pork.py hello.yml Hello world """ # standard library import os, sys # 3rd parties import yaml _django_configured = False engines = [] # decorator to maintain the list of engines from renderer methods def engine(func): if func.__name__ not in engines: engines.append(func.__name__) return func class Renderer: def __init__(self, config, values): # split the path into a directory and a filename and get it rendered self.head, self.tail = os.path.split(config["template"]) self.values = values self.config = config self.engine = config.get('engine','django') def render(self): return eval("self.%s()" % self.engine) def spit(self, output=None): if output is None: output = self.render() if self.config.has_key("target"): os.makedirs(os.path.split(self.config["target"])[0]) fh = open(self.config["target"],'w') fh.write(output) fh.close() print "### generated [%s]" % self.config["target"] else: print output # renderers @engine def django(self): # django from django.conf import settings from django.template.loader import render_to_string # use of django's templates without settings requires this # configury-pokery global _django_configured if not _django_configured: settings.configure() _django_configured=True settings.TEMPLATE_DIRS=[self.head] return render_to_string(self.tail, self.values) @engine def jinja2(self): # jinja2 from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader(self.head)) template = env.get_template(self.tail) values = self.values return template.render(**values) @engine def mako(self): # mako from mako.lookup import TemplateLookup lookup = TemplateLookup(directories=[self.head], collection_size=1) template = lookup.get_template(self.tail) values = self.values return template.render(**values) @engine def python(self): # string.Template import string file = "%s/%s" % (self.head, self.tail) values=self.values return string.Template(open(file).read()).safe_substitute(values) def usage(): sys.stderr.write("Usage: pork.py [ ...]\n") sys.exit() def main(): assert len(sys.argv) > 1, usage() for file in sys.argv[1:]: try: generate(file) except Exception, e: sys.stderr.write("### Problem with file [%s], error was:\n" % file) sys.stderr.write("### %s\n" % e) def generate(file): # if this stuff barfs (bad yaml, no file, etc) we're just going to # let python + yaml throw their errors # NB: load_all returns a generator that can't be cast to a list, but I # want it as a list damn it, hence the comprehension docs = [doc for doc in yaml.load_all(open(file))] # now do all our assertions in the hope that we have clean data # all other errors can be caught by the template engines or python; # we only care about pork.py's requirements assert len(docs) == 2, "yaml file must contain only 2 documents" config, values = docs assert isinstance(config,dict), "config document must be dictionary" assert isinstance(values,dict), "values document must be dictionary" assert config.has_key("template"), "config document must contain template" assert config.get('engine','django') in engines, \ "supported engines are %s" % engines # the class does everything now # initialises with the documents, .spit does the rendering and the # stdout/file output Renderer(config,values).spit() if __name__ == '__main__': main()