#!/usr/bin/env python
#
# Template Compiler
# Copyright (c) 2003 Computel Standby BV
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
## We explicitly claim no rights to the output generated by this
## program.

#
# The template style for this template compiler is based on the
# phpBB template style. There are two types of control statements
# that can be used in the templates. These are explained below. The
# dynamic content for the template can be stored in a dictionary
# which is then applied to the template. In the examples we call
# this dictionary: content_dict
#
# Control structures:
# - Variable substitution:
#   Every occurrence of
#    {VARIABLE_NAME}
#   will be replaced with the content of
#    content_dict['VARIABLE_NAME']
#
# - Loops:
#   A loop will show the content of a list of sub-dictionaries.
#   This can be best explained with an example. Take for example
#   the following content_dict:
#
#    content_dict = {
#       'TITLE':    'This is a title',
#       'ITEMS':    [{
#             'NAME':    'Item one',
#             'VALUE':   'Value one'
#          }, {
#             'NAME':    'Item two',
#             'VALUE':   'Value two'
#          }]
#    }
#
#   Which can be applied to the following example template:
#
#    <html><head>
#      <title>{TITLE}</title>
#    </head><body>
#      <h1>{TITLE}</h1>
#      <table>
#      <!-- BEGIN ITEMS -->
#        <tr>
#          <td>{ITEMS.NAME}</td>
#          <td>{ITEMS.VALUE}</td>
#        </tr>
#      <!-- END ITEMS -->
#      </table>
#    </body></html>
#
#   This will result the following output:
#
#    <html><head>
#      <title>This is a title</title>
#    </head><body>
#      <h1>This is a title</h1>
#      <table>
#        <tr>
#          <td>Item one</td>
#          <td>Value one</td>
#        </tr>
#        <tr>
#          <td>Item two</td>
#          <td>Value two</td>
#        </tr>
#      </table>
#    </body></html>
#

import sys, os, time, re

# Check command-line
if len(sys.argv) < 2 or len(sys.argv) > 3:
	print "Usage: %s <template.tpl> [output.py]" % sys.argv[0]
	print ""
	print "This will process the file template.tpl and produce a compiled version"
	print "in the file template.py. This file will contain one function: apply."
	print ""
	print "Calling the apply function with a content-dictinary as parameter will"
	print "return the template data with the supplied content applied."
	print ""
	print "For example:"
	print "  import template"
	print "  output = template.apply(content_dict)"
	print "  print output"
	print ""
	print "Look in the supplied example and the source for more information."
	print ""
	sys.exit(1)

# Construct filenames and open files
infilename = sys.argv[1]
if len(sys.argv) > 2:
	outfilename = sys.argv[2]
else:
	outfilename = os.path.splitext(sys.argv[1])[0] + '.py'

try:
	infile = file(infilename, 'r')
except:
	print "Input file '%s' can not be opened for reading" % infilename
	sys.exit(2)

try:
	outfile = file(outfilename, 'w')
except:
	print "Output file '%s' can not be opened for writing" % outfilename
	sys.exit(3)

# Prepare regular expressions
re_vars = re.compile(r'\{([A-Z0-9_\.]+)\}')
re_begin = re.compile(r'<!-- BEGIN ([A-Z0-9_\.]+) -->')
re_end = re.compile(r'<!-- END ([A-Z0-9_\.]+) -->')

# Timing
starttime = time.time()

# Template header
print >> outfile, '#'
print >> outfile, '# This is the compiled version of template file %s' % os.path.basename(infilename)
print >> outfile, '#'
print >> outfile, ''

# Begin apply function
print >> outfile, 'def apply(data, require_data=True):'
print >> outfile, '	"Apply data to this template"'
print >> outfile, '	'
print >> outfile, '	# The output from this template'
print >> outfile, '	out = ""'
print >> outfile, '	'

# Use special dict type which does not return KeyError?
print >> outfile, '	if not require_data:'
print >> outfile, '		class safeDict(dict):'
print >> outfile, '			"dict type which does not return KeyError"'
print >> outfile, '			'
print >> outfile, '			def __getitem__(self, key):'
print >> outfile, '				try:'
print >> outfile, '					return dict.__getitem__(self, key)'
print >> outfile, '				except KeyError:'
print >> outfile, '					return ""'
print >> outfile, '	else:'
print >> outfile, '		safeDict = dict'
print >> outfile, '	'

# Make data a safeDict
print >> outfile, '	# A stack containing the data for each loop we are in'
print >> outfile, '	datastack = [safeDict(data)]'
print >> outfile, '	'

# Include make_datablock funtion here
print >> outfile, '	def make_datablock(current, new, blockname):'
print >> outfile, '		"""Copy the dictionary current and add all keys/values from new to it,'
print >> outfile, '		with blockname and a dot prepended to the key."""'
print >> outfile, '		'
print >> outfile, '		out = current.copy()'
print >> outfile, '		for key in new.iterkeys():'
print >> outfile, '			out[blockname+"."+key] = new[key]'
print >> outfile, '		'
print >> outfile, '		return safeDict(out)'
print >> outfile, '	'

# Track line number, indent position and block names
linenum = 0
indent_size = 1
indent = '\t' * indent_size
blocknames = []

# Process the template file
for line in file(sys.argv[1]).xreadlines():
	# Count lines
	linenum += 1

	# Strip End-Of-Line characters
	while len(line) > 0 and (line[-1] == '\n' or line[-1] == '\r'):
		line = line[:-1]
	
	# Parse line for block begin/end statements
	beginmatch = re_begin.match(line.strip())
	endmatch = re_end.match(line.strip())

	if beginmatch is not None:
		# This is a BEGIN line
		blockname = beginmatch.group(1)
		blocknames.append(blockname)
		loopname = blockname.replace('.', '_')

		print >> outfile, indent
		print >> outfile, indent + "datastack[-1]['%s.TPL_AUTO_ID'] = 1" % blockname
		print >> outfile, indent + "if not require_data and type(datastack[-1].get('%s', None)) != list: datastack[-1]['%s'] = []" % (blockname, blockname)
		print >> outfile, indent + "for %s in datastack[-1]['%s']:" % (loopname, blockname)

		# Indent
		indent_size += 1
		indent = '\t' * indent_size

		# Datablock processing
		print >> outfile, indent + "# Append a new datablock"
		print >> outfile, indent + "datastack.append(make_datablock(datastack[-1], %s, '%s'))" % (loopname, blockname)
		print >> outfile, indent

	elif endmatch is not None:
		# This a an END line
		blockname = endmatch.group(1)
		if blockname != blocknames.pop():
			raise RuntimeError, 'Closing the wrong block at line %d' % linenum
		
		# Restore the previous datablock
		print >> outfile, indent
		print >> outfile, indent + "# Restore the previous datablock"
		print >> outfile, indent + "datastack.pop()"
		print >> outfile, indent + "datastack[-1]['%s.TPL_AUTO_ID'] += 1" % blockname

		# Un-Indent
		indent_size -= 1
		indent = '\t' * indent_size

		print >> outfile, indent
	else:
		# Escape characters and replace variables
		line = line.replace("\\", "\\\\").replace("'", "\\'")
		escaped_line = line.replace('%', '%%')
		varline = re_vars.sub('%(\\1)s', escaped_line)

		# Add line to output script
		if varline == escaped_line:
			print >> outfile, indent + "out += '%s\\n'" % line
		else:
			print >> outfile, indent + "out += '%s\\n' %% datastack[-1]" % varline

# Close the apply function
print >> outfile, '	'
print >> outfile, '	# Klaar!'
print >> outfile, '	return out'

# Footer
print >> outfile, ''
print >> outfile, '# Processing this template took %.2f seconds' % (time.time() - starttime)
