#!/usr/bin/env python
# -*- coding: utf-8 -*-

# PyTemplate, Simple templates for Python
# Copyright (C) 2005, Yoan BLANC, doSimple
#
# 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

__author__ = "Yoan Blanc"
__date__ = "Le 13 avril 2005"
__license__ = "http://opensource.org/licenses/gpl-license.php"

import re

class Template:
	"""
	A Class to parse template
	"""
	
	opener = "<!--%"
	closer = "%-->"
	hardcloser = "(?:[^%][^\-]{2}[^>])"
	
	# loop
	loopopen = "loop:"
	loopclose = "endloop"
	
	# if
	ifopen = "if:"
	ifelif = "elseif:"
	ifelse = "else"
	ifclose = "endif"
	
	# recursion
	recopen = "recursion:"
	recloop = recopen + "loop"
	recbody = recopen + "body"
	recendloop = recopen + "endloop"
	recclose = "endrecursion"
	
	def __init__(self, filename):
		tpl = open(filename)
		self.data = tpl.read()
		self._context = {}
	
	def apply(self, values):
		"""
		Apply values to template
		"""
		for k,v in values.items():
			if type(v) is str or type(v) is unicode :
				self.replace(k, v)
			elif type(v) is int or type (v) is float :
				self.replace(k, str(v))
			elif type(v) is list:
				self.loop(k, v)
			else:
				print k + " unknow "
				print type(v)
	
	def setContext(self, dict):
		"""
		Add new elements to context
		"""
		self._context.update(dict)
	
	def __parseif(self, text):
		"""
		Apply if statement
		"""
		
		ifregex = "(" + self.opener + self.ifopen + "(.*?)" + self.closer + "(.*?)" + self.opener + self.ifclose + self.closer + ")"
		elifregex = self.ifelif + "(.*?)" + self.closer + "(.*?)" + self.opener
		thenregex = "(.*?)" + self.opener + "(" + self.ifelif + "|" + self.ifelse + "|" + self.ifclose + ")"
		elseregex = self.opener + self.ifelse + self.closer + "(.*)"
		
		p = re.compile(ifregex, re.DOTALL | re.MULTILINE)
		
		ifs = p.findall(text)
		
		result = ""
		
		def evalformat(txt):
			has_key = re.compile("\[(.*?)\]")
			
			text = has_key.sub (r"self._context.has_key(\1) and self._context[\1]", txt)
			
			return text
		
		for ifstat in ifs:
			if(eval(evalformat(ifstat[1]))):
				np = re.compile(thenregex, re.DOTALL | re.M)
				npr = np.search(ifstat[2])
				
				if(npr is not None):
					result = npr.group(1)
				else:

					result = ifstat[2]
				
			else:
				eip = re.compile(elifregex, re.DOTALL | re.M)
				elifs = eip.findall(ifstat[2])
				
				find = 0
				
				for elifstat in elifs:
					if(eval(evalformat(elifstat[0]))):
						result = elifstat[1]
						find = 1
						break
				
				if not find:
					e = re.compile(elseregex, re.DOTALL | re.M)
					
					ev = e.search(ifstat[2])
					if ev is not None:
						result = ev.group(1)
					else:
						result = ''
			
			text = text.replace(ifstat[0],result)		
		
		return text
	
	def recursive(self, name, value):
		"""
		Apply recursive template
		"""
		
		recursionloop = "(.*?)" + self.opener + self.recloop + self.closer + "(.*?)" + self.opener + self.recendloop + self.closer + "(.*?)"
		recursion = "(" + self.opener + self.recopen + name + self.closer + recursionloop + self.opener + self.recclose + self.closer + ")"
		
		rec = re.compile(recursion, re.DOTALL | re.M)
		
		r = rec.findall(self.data)
		
		oldcontext = self._context
		
		def recurs (data, triple):
			result = triple[0]
			
			for line in data:
				body = triple[1]
				rec = ""
				
				self._context = line[0]
				body = self.__parseif(body)
				
				if(len(line) > 1):
					rec = recurs(line[1], triple)
					
				line[0][self.recbody] = rec
				
				for k,v in line[0].items():
					body = body.replace(self.opener + k + self.closer, str(v))
				
				result += body
			
			result += triple[2]
			
			return result
		
		self._context = oldcontext
		
		for rfind in r:
			
			text = recurs(value, rfind[1:])
			
			self.data = self.data.replace(rfind[0], text)
			
	
	def replace(self, name, value):
		"""
		replace the key(s) with the given value
		"""
		
		self.data = self.data.replace(self.opener + name + self.closer, value)
	
	def loop(self, name, values):
		"""
		Apply the values to that loop !
		"""
		
		loop = self.__getloop(name)
		
		oldcontext = self._context
		
		for l in loop:
			data = ""
			for line in values:
				self._context = line
				linedata = self.__parseif(l)
				for k,v in line.items():
					linedata = linedata.replace(self.opener + k + self.closer, v)
				data += linedata
			
			regex = self.opener + self.loopopen + name + ".*?" + self.loopclose + self.closer
			
			p = re.compile(regex, re.DOTALL | re.M)
			
			self.data = p.sub(data, self.data)
		
		self._context = oldcontext
	
	def __getloop(self, name):
		"""
		Init the loops
		"""
		regex = self.opener + self.loopopen + "(" + name + ")" + self.closer + "(.*?)" + self.opener + self.loopclose + self.closer
			
		loop = re.compile(regex, re.DOTALL | re.MULTILINE)
			
		listloop = loop.findall(self.data)
		
		loops = []
			
		for n in listloop:
			loops.append(n[1])
		
		return loops
		
	
	def write(self):
		"""
		Return the string output
		"""
		
		text = self.__parseif(self.data)
		
		emptytags = re.compile(self.opener + ".*?" + self.closer, re.DOTALL)

		text = emptytags.sub("", text)

		return text

def val_format(val):
	
	if type(val) is list:
		return _list_format(val)
	elif type(val) is dict:
		return _dict_format(val)
	else:
		return "val = " + str(val)

def _dict_format(dict):
	ret = "data = {\n"
	
	for k, v in dict.items():
		ret += "\t'" + str(k) + "' : " + str(v) + "\n"
	
	ret += "}"
	
	return ret

def _list_format(list):
	ret = "list = [\n"
	
	for i in list:
		ret += "\t" + str(i) + ",\n"
	
	ret += "]"
	
	return ret

def file_content(filename):
	fp = file(filename)
	
	f = fp.readlines()
	
	return "".join(f)

def to_html(txt):
	
	if txt is not None:
		return txt.replace('<','&lt;').replace('>','&gt;')
	else:
		return ""

def main():
	
	# Titles
	
	title = {}
	
	title['intro'] = "Introduction"
	title['basis'] = "Opération de remplacement"
	title['loop'] = "Boucle"
	title['if'] = "Instruction conditionnelle"
	title['recursive'] = "Récursivité"
	
	# Descriptions
	
	desc = {}
	
	desc['intro'] = """<p>
	Cet article a pour but de présenter l'usage des templates itératifs
	en Python. Toutes les informations importantes se trouve dans <a href=".">l'article
	parent</a> que je vous invite à consulter.
</p>"""
	
	desc['basis'] = """<p>
	L'opération de base que doit assurer un système de template est
	de pouvoir effectuer des remplacements d'une clé par une valeur.
	Ce mode là est un mode "squelette", c.-à-d. qu'il va être utilisé
	dans la structure de base d'un site internet. 
<p>
<p class="example">
	<strong>Par exemple :</strong> la page générale du site dans laquelle il y a une
	entête, un menu et un contenu. Le squelette va comporter toute la
	structure <abbr title="Extensible HyperText Markup Language">XHTML</abbr>
	avec les trois éléments de contenu à remplir.
</p>"""
	desc['loop'] = """<p>
	La boucle, second élément indispensable, dès qu'il s'agit d'interagir
	avec une banque de données. Cet élément n'ajoute pas beaucoup de complexité
	mais améliore grandement les possiblités d'utilisations.
</p>
<p class="example">
	<strong>Exemple d'utilisation :</strong> un fil <abbr title="Rich Site Summary">RSS</abbr>
	contenant les dernières nouvelles/modifications d'un <i>weblog</i> où les éléments de la
	boucle proviendrait d'une base de données <abbr title="Structured Query Language">SQL</abbr>.
	Ci-dessous l'exemple affiche un menu. Vous noterez la présence d'un <code>if</code> dont
	l'usage est expliqué au point suivant.
</p>
<p class="warning">
	<strong>Attention :</strong> si deux (ou plus) boucles ont le même nom, la
	seconde prendra le contenu de la première. En jouant avec ça, vous trouverez
	le moyen simple de générer un même contenu dans une page sans se fatiguer.
</p>"""
	desc['if'] = """<p>
	Quand il s'agit d'amener un peu plus de flexibilité au niveau du résultat
	difficile de faire l'impasse sur les instructions conditionnelles (<code>if</code>).
</p>
<p class="example">
	Ci-dessous, un exemple d'affiche l'information sur le nombre de commentaires de manière
	française (en évitant le trop vu et disgracieux <cite>0 commentaire(s)</cite>).
</p>"""
	desc['recursive'] = """<p>
	Le meilleur est gardé pour la fin, la récursivité. L'outil indispensable pour afficher
	une arborescence de fichiers, la carte d'un site ou autre. Mais cette efficacité n'est
	pas évidente à manipuler et demande de ne pas s'y jeter à la légère. Un nœud de l'arbre
	se forme ainsi : <code>({<i>valeurs</i>},[<i>sous-branches</i>])</code> alors qu'une
	feuille (fin de l'arbre qui ne possède pas de branches) ainsi : <code>({<i>valeurs</i>},)</code>.
</p>
<p class="warning">
	Un conseil, jetez un œil à <a href="/articles/Template/#recursif">la version</a> utilisant
	<abbr title="PHP Hypertext Preprocessor">PHP</abbr> qui peut vous aider à comprendre
	le doux mix entre les listes et les dictionnaires.
</p>"""
	
	# Data
	
	data = {}
	
	data['basis'] = {}
	data['basis']['titre'] = "Titre"
	data['basis']['description'] = "Une <strong>description</strong> selon la <em>loi</em>."
	data['basis']['adresse'] = "Rue des Catalans 38, 2800 Les Bois"
	
	data['loop'] = []
	data['loop'].append({'url':"http://www.lzi.ch",'texte':"LolZ",'titre':"Plop !"})	
	data['loop'].append({'url':"http://www.dosimple.ch",'texte':"doSimple"})
	
	data['if'] = {}
	data['if']['compteur'] = 2
	
	data['recursive'] = []
	data['recursive'].append(({'nom':"Une"},))
	data['recursive'].append(({'nom':"tres"},[]))
	data['recursive'][1][1].append(({'nom':"belle"},[]))
	data['recursive'][1][1][0][1].append(({'nom':"liste","strong":1,"em":1},))
	data['recursive'][1][1].append(({'nom':"et"},[]))
	data['recursive'][1][1][1][1].append(({'nom':"cool","em":1},))
	data['recursive'][1][1].append(({'nom':"mais","strong":1},[]))
	data['recursive'][1][1][2][1].append(({'nom':"aussi"},[]))
	data['recursive'][1][1][2][1][0][1].append(({'nom':"génial.","em":1},))
	
	# Template
	
	tpl = {}
	tpl['basis'] = "template/basis.html"
	tpl['loop'] = "template/loop.html"
	tpl['if'] = "template/if.html"
	tpl['recursive'] = "template/recursive.html"
	
	# -----------------------------------------------------------------------------------------
	
	print "Content-type: text/html;charset=utf-8\r\n"
	
	maintpl = Template("template/index.html")
	
	result = {}
	
	result['demo'] = []
	
	res = {}
	res['date'] = __date__
	res['author'] = __author__
	
	# Intro
	
	result['demo'].append(
		{'title':title['intro'],
		'description':desc['intro']}
	);
	
	# Basis
	
	elem = 'basis'
	
	templ = Template(tpl[elem])
	templ.apply(data[elem])
	
	result['demo'].append(
		{'title':title[elem],
		'id':"simple",
		'description':desc[elem],
		'template':to_html(file_content(tpl[elem])),
		'code':to_html(val_format(data[elem])),
		'result':templ.write(),
		'result.escaped':to_html(templ.write())})
	
	# Loop
	
	elem = 'loop'
	
	templ = Template(tpl[elem])
	templ.loop('menu',data[elem]);
	
	result['demo'].append(
		{'title':title[elem],
		'id':"loop",
		'description':desc[elem],
		'template':to_html(file_content(tpl[elem])),
		'code':to_html(val_format(data[elem])),
		'result':templ.write(),
		'result.escaped':to_html(templ.write())})
	
	# if
	
	elem = 'if'
	
	templ = Template(tpl[elem])
	templ.setContext(data[elem])
	templ.apply(data[elem]);
	
	result['demo'].append(
		{'title':title[elem],
		'id':"if",
		'description':desc[elem],
		'template':to_html(file_content(tpl[elem])),
		'code':to_html(val_format(data[elem])),
		'result':templ.write(),
		'result.escaped':to_html(templ.write())})

	
	# recursive

	elem = 'recursive'
	
	templ = Template(tpl[elem])
	
	templ.recursive('biglist',data[elem]);

	result['demo'].append(
		{'title':title[elem],
		'id':"recursion",
		'description':desc[elem],
		'template':to_html(file_content(tpl[elem])),
		'code':to_html("""list = [
	({'nom': 'Une'},),
	({'nom': 'tres'},
		[
			({'nom': 'belle'},
				[
					({'nom': 'liste','strong' : 1, 'em' : 1},)
				]
			),
			({'nom': 'et'},
				[
					({'nom': 'cool', 'em' : 1},)
				]
			),
			({'nom': 'mais','strong':1},
				[
					({'nom': 'aussi'}, 
						[
							({'nom': 'génial.','em':1},)
						]
					)
				]
			)
		]
	)
]"""),
		'result':templ.write(),
		'result.escaped':to_html(templ.write())})
	
	# --
	
	maintpl.apply(res);
	maintpl.loop('demo',result['demo'])
	maintpl.loop('toc', result['demo'])
	
	print maintpl.write()

if __name__ == "__main__" : main()
