Skip to content

Instantly share code, notes, and snippets.

@augustomen
Created October 20, 2014 18:07
Show Gist options
  • Save augustomen/e84eaf10f7184aff1074 to your computer and use it in GitHub Desktop.
Save augustomen/e84eaf10f7184aff1074 to your computer and use it in GitHub Desktop.
templatetree - builds a inheritance tree of all templates in a Django project or folder
# coding: utf-8
__author__ = "Augusto Men"
__version__ = "$Revision: 0.1 $"
__date__ = "$Date: 2014/09/29 $"
DESCRIPTION = """Builds a tree of template files based on inheritance."""
import os
import re
import sys
import argparse
from fnmatch import fnmatch
QUOTED = re.compile(r'^[\'\"](.*?)[\"\']$')
def unquote(s):
"""Returns the inner string if s is between quotes, or otherwise None."""
match = QUOTED.match(s)
if match:
return match.group(1)
class TemplateItem(object):
PATTERN = re.compile(r'{% extends ([^%]+)%}')
TEMPLATE_SUBDIR = re.compile(r'templates/(.*)')
def __init__(self, filename):
# set the standard
self.filename = filename
self.children = []
self.template_name = None
self.extends = None
self.extends_var = None
match = self.TEMPLATE_SUBDIR.search(filename.replace('\\', '/'))
if match:
self.template_name = match.group(1)
self.read()
def __repr__(self):
return 'TemplateItem(%s)' % self.filename
def read(self):
with open(self.filename, 'r') as f:
text = f.read(256)
match = self.PATTERN.search(text)
if match:
extends = match.group(1).strip()
template_name = unquote(extends)
if template_name:
self.extends = template_name
else:
self.extends_var = extends
def scan_path(path, mask='*'):
"""An iterable to the matched files (relative path)."""
for path, folders, files in os.walk(path):
for filename in files:
if fnmatch(filename, mask):
yield os.path.join(path, filename)
for folder in folders:
for filename in scan_path(os.path.join(path, folder), mask):
yield filename
def process_path(path, mask=None):
"""Locates template files and organizes them into a tree."""
mask = mask or '*.html'
templates = {}
for filename in scan_path(path, mask):
item = TemplateItem(filename)
if item.template_name:
templates[item.template_name] = item
roots = []
variables = []
# Organize the templates in a tree
for template_name, item in templates.iteritems():
if item.extends_var:
variables.append(item)
elif item.extends in templates:
templates[item.extends].children.append(item)
else:
roots.append(item)
roots = sorted(roots, key=lambda i: (
i.extends or '', i.template_name or ''))
variables = sorted(variables, key=lambda i: (
i.extends_var or '', i.template_name or ''))
return roots, variables
def write_output(output, roots, variables=None, level=0):
"""Writes the output in HTML list-style.
roots and variables must be a list of TemplateItem.
"""
if roots or variables:
output.write('%s<ul>\n' % (' ' * level))
level += 1
for item in roots:
output.write('%s<li>%s</li>\n' % (' ' * level, item.template_name))
write_output(output, item.children, level=level + 1)
if variables:
output.write('<li>Templates descending from variables:</li>\n')
output.write('%s<ul>\n' % (' ' * level))
level += 1
for item in variables:
output.write('%s<li>%s = %s</li>\n' % (
' ' * level, item.extends_var, item.template_name))
level -= 1
output.write('%s</ul>\n' % (' ' * level))
level -= 1
output.write('%s</ul>\n' % (' ' * level))
def main(argv=[]):
parser = argparse.ArgumentParser(description=DESCRIPTION)
parser.add_argument(
'path', default='',
help="Path to be scanned. If not informed, current will be used.")
parser.add_argument(
'--mask', default='*.html',
help="HTML template mask. Default: *.html")
parser.add_argument(
'--output', metavar='FILENAME', default='',
help="Output file. Default: console")
args = parser.parse_args(argv)
path = args.path
if not path:
path = os.getcwd()
sys.stderr.write("Scanning...\n")
roots, variables = process_path(path, args.mask)
sys.stderr.write("Writing output...\n")
if args.output:
output = open(args.output, 'w')
else:
output = sys.stdout
write_output(output, roots, variables)
if __name__ == "__main__":
main(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment