Skip to content

Instantly share code, notes, and snippets.

@bookwar
Last active January 15, 2022 22:32
Show Gist options
  • Save bookwar/fa0368fb7c5376c78a4e6ddaba7fe75f to your computer and use it in GitHub Desktop.
Save bookwar/fa0368fb7c5376c78a4e6ddaba7fe75f to your computer and use it in GitHub Desktop.
Jinja renderer

J2Render

Use jinja2 to render arbitrary templates using parameters provided in YAML format.

Usage

Run command:

$ j2render -v vars/ -t templates/ -o output/

Rule

for <resource> in templates/*:
  for <item> in vars/<resource>/*:
    for <file> in templates/<resource>/*:
      _output/<resource>/<item>/<file> = render(
        templates/<resource>/<file>,
        vars/global.yaml + vars/<resource>/<item>.yaml
       )

Example

YAML files layout

vars
├── global.yaml
├── resourceA
│   ├── item1.yml
│   └── item2.yml
└── resourceB
    ├── item_1.yml
    └── item_2.yml

Templates layout

templates
├── resourceA
│   ├── fileX.j2
│   └── fileY.j2
└── resourceB
    ├── fileW.j2
    └── fileZ.j2

Output

_output
├── resourceA
│   ├── item1
│   │   ├── fileX(item1, global)
│   │   └── fileY(item1, global)
│   └── item2
│       ├── fileX(item2, global)
│       └── fileY(item2, global)
└── resourceB
    ├── item_1
    │   ├── fileW(item_1, global)
    │   └── fileZ(item_1, global)
    └── item_2
        ├── fileW(item_2, global)
        └── fileZ(item_2, global)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from jinja2 import Environment, FileSystemLoader
import yaml
import logging
import os
import errno
import argparse
def write_file(filepath, data):
'''Write data to file, creating folders if don't exist'''
logging.info("Writing {0}".format(filepath))
try:
os.makedirs(os.path.dirname(filepath))
except OSError as e:
if e.errno != errno.EEXIST:
raise
with open(filepath, 'w') as fd:
fd.write(data)
fd.write("\n")
class J2RenderError(Exception):
pass
class Env():
ITEM_EXT = ['yml', 'yaml']
TMPL_EXT = ['j2', 'jinja']
def __init__(self, output, templates, variables):
'''Initialize the configuration env
- parse templates tree to get the list of resources
- parse variables tree to get a dictionary of common data
'''
self.output = output
self.templates = templates
self.variables = variables
self.resources = next(os.walk(self.templates))[1]
common_files = [
filename for filename in next(os.walk(self.variables))[2]
if filename.endswith(tuple(self.ITEM_EXT))
]
self.common = {
'system': os.environ,
}
for filename in common_files:
key = os.path.splitext(os.path.basename(filename))[0]
with open(os.path.join(self.variables, filename)) as fd:
logging.info("Register common file: {0}".format(key))
self.common[key] = yaml.load(fd)
def render_resource(self, resource, items):
'''Render all templates under $templates/$resource/ folder
For every item in the items list, create a rendered variant of
the resource folder.
'''
tmpl_path = os.path.join(self.templates, resource)
item_path = os.path.join(self.variables, resource)
for item in items:
with open(os.path.join(item_path, item)) as fd:
item_data = yaml.load(fd)
J2Env = Environment(loader=FileSystemLoader(tmpl_path))
for template in J2Env.list_templates(extensions=self.TMPL_EXT):
output_data = J2Env.get_template(template).render(
data=item_data,
**self.common
)
output_file = os.path.join(
self.output,
resource,
os.path.splitext(item)[0],
os.path.splitext(template)[0],
)
write_file(output_file, output_data)
def render(self, resource=None, item=None):
if resource:
if resource not in self.resources:
raise(J2RenderError("unknown resource {0}".format(resource)))
else:
resources = [resource]
else:
resources = self.resources
for resource in resources:
if not item:
items = []
item_path = os.path.join(self.variables, resource)
if os.path.isdir(item_path):
for filename in next(os.walk(item_path))[2]:
if filename.endswith(tuple(self.ITEM_EXT)):
items.append(filename)
else:
items = [item]
self.render_resource(resource, items)
def cli():
parser = argparse.ArgumentParser(
description='Render resource templates with items',
)
parser.add_argument('-d', '--debug', action='store_true',
help='enable debug output')
parser.add_argument('-o', '--output', default='_output',
help='path to results')
parser.add_argument('-t', '--templates', default='templates',
help='path to templates')
parser.add_argument('-v', '--variables', default='variables',
help='path to variables')
parser.add_argument('-r', '--resource', default=None,
help='resource to render')
parser.add_argument('-i', '--item', default=None,
help='name of the item file')
args = parser.parse_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
env = Env(
output=args.output,
templates=args.templates,
variables=args.variables,
)
env.render(args.resource, args.item)
if __name__ == "__main__":
cli()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment