Package: template-nest
Template-Nest Templating SystemTL;DRYou should definitely use Template-Nest because it's the most logical templating system in existence.Create templates that have no code in them whatsoever. No "template ifs", "template loops". No "template includes". Nothing but holes to fill in.No code in your templates means they are language independent. Use the same templates with Java, Ruby, Perl, Javascript...If templating html, produce only ordinary html files that can be displayed independently in a browser. (ie. no files with weird extensions)Build views as a tree structure of templates (exactly as the DOM does!) limiting the number of templates needed and avoiding repetition.There is no "template language" to use or learnEliminate the confusion of where to put processing. Get rid of the mess this confusion produces.For intrigue, also see: "Building a template-less platform with Template-Nest" at the bottom of this page.SYNOPSIS<!-- page.html --><html><head><style>div{padding:20px;margin:20px;background-color:yellow;}</style></head><body><% contents %></body></html><!-- box.html --><div><% title %></div>fromtemplate_nestimportNestnest=Nest({template_dir=>'/html/templates/dir',fixed_indent=>1})page={'NAME':'page','contents':[{NAME=>'box',title=>'First nested box'}]}page['contents'].append({NAME=>'box',title=>'Second nested box'})print(nest.render(page))Output:<html><head><style>div{padding:20px;margin:20px;background-color:yellow;}</style></head><body><div>First nested box</div><div>Second nested box</div></body></html>In depthSo admittedly my first statement on this page is a bold claim. Of course this is just my (narcissistic?) opinion - but read on to see if you agree.I originally developed Template-Nest in Perl (seehere. However, Template-Nest is a philosophy more than anything, and as such is language independent. (In fact, language independence is a key feature of Template-Nest as I am about to explain!)I decided to port it over to Python for several reasons:I want to use the same templates with a Django project that are already being used in a Perl project. (The most practical reason).I think this is a great first module to get my feet wet publishing on PyPI. (The most humble reason).I want this templating philosophy to take over the world. (The most ambitious reason!)Seriously though, regarding the last point: Even if this particular module does not get adopted (which it probably won't), it would be great if at least some people somewhere gained even a tiny modicum of exposure to the underlying philosophy. Even if all you do is read this documentation, and go away and think about what you read, then I would regard this exercise as having been worthwhile.This isn't because I have some lofty goal of wanting to improve humanity. The reason is much more personal and straightforward: I am tired of tearing my hair out wading through messy, ugly templates! As a professional coder, I have to deal with these damn things on a daily basis, and sigh and groan to myself as I believe there is a better way. Please for the sake of sanity, stop torturing me with these gargantuan spaghetti heaps. Consider using Template-Nest instead.So what's the problem?I think there are actually 2 problems:Everyone is busily stuffing their "templates" full of code. I believe thisis not a good ideaviolates MVC (sorry, but you haven't actually separated control from view)means you probably need to learn a new mini language (the "templating language") on top of Python and/or whatever else.makes displaying your raw template (e.g. in a browser) difficult (the browser can't handleif,thenetc. statements written in your "templating language"gives you an awkward file type with an uncomfortable extension (e.g. seethis debateon the confusion of what extension is best to use for django templates.)makes editing your raw template difficult (you have a mixture of markup and code, so which syntax highlighter do you pick?)most importantly - because this is what causes the mess - raises the question of what is "program processing" vs. what is "template processing". Really, if you are going to have "template processing", then at a minimum you need a guideline as to how to identify it. If you don't have one (and most systems don't because if you are already putting code in templates, you probably haven't thought much about consequences), then the result is a mess: in one template you've got anif,thenstructure, while in another that same logic is missing because the coder decided to put it in the "program processing" and hand the template the result. Now add in "template loops" and whatever other nonsense your "templating language" provides, and then multiply it by hundreds of templates. The outcome is total incomprehensibility.Most templating systems producetightly coupledtemplatesThis is an issue I wasn't even really aware of until I discovered Template-Nestdoesn'tdo this, and then noticed how much better it was without the coupling. So what do I mean bytightly coupled? Let's take Django. TheTutorials Point documentation on Django templateshelpfully tells us "A templating system cannot be complete without template inheritance". No disrespect to Tutorials Point here; they have great tutorials, and they are just echoing the commonly held view. But it is wrong! Youcandesign a templating language without template inheritance. Template-Nest is one such templating system!Basically most templating systems encourage you to refer to templates from other templates. This might be done with an "include" - something like this:<!-- page.djt --><div>Main Page Title</div><div>{% include content.djt %}</div><div>Some Footer</div>or with an "extend", like this:<!-- child.djt --->{% extends parent.djt %}<div>Some Childish Stuff</div>In either case, you are essentially hard coding the relationship between your templatesinsidethe templates. E.g. what happens if one time you want to includeother_content.djtinstead ofcontent.djtinpage.djtabove? Yes you can do it with inheritance, but it's ugly and you are going to end up with one more template.The point being, in my opinion the relationship between templates isalso control processing. The templates should not know about each other. It is the job of the controller to decide how to slot the templates together. So templateincludeandextendsshould also not be necessary.How is Template-Nest different?I think the concept of templating originally started with the "one template per view" (or perhap "page") paradigm. It's compelling to save files with names like "home_page_template" etc. So if you do that, you need to have processing in your template, because it's going to have stuff in it thats the same as "blog_page_template". It seems as if most templating systems start with this premise, and the question of how to combine templates is almost tacked on as an afterthought.With Template-Nest we deal with the issue of combining templates first and foremost; and we assume you are going to create tree-like structures of templates. Remember, HTML is tree structured, the browser treats pages as trees of elements (ie the DOM), so why are we attempting to create what's sent to the client from a monolithic slab? No wonder our templates end up stuffed full of code.AN EXAMPLELets say you have a template for a letter (if you can remember what that is!), and a template for an address. Using the standard Django templating system you might do something like this:in letter.html{% include address.djt %}

    Dear {{username}}

    ....However, in Template-Nest there's noinclude- there are only tokens to fill in, so you would havein letter.html:<!--% address %-->Dear<!--% username %-->...I specify that I want to useaddress.htmlwhen I fill out the template, thus:letter={'NAME':'letter',# this specifies "letter.html" (provided template_ext=".html")'username':'billy','address':{'NAME':'address',#  "address.html"# any other variables in 'address.html'}}print(nest.render(letter))This is much better, because nowletter.htmlis not hard-coded to useaddress.html. You can decide to use a different address template without needing to change the letter template.Commonly used template structures can be labelled (main_pageetc.) stored in your code in functions, dicts, object attributes or whatever method seems the most convenient.Another exampleThe idea of a "template loop" comes from the need to e.g. fill in a table with an arbitrary number of rows. So using Django's standard templating system you might do something like:in employee_list.djt:<table><tr><th>Name</th><th>Job</th><tr>{% for employee in employees %}<tr><td>{{ employee.name }}</td><td>{{ employee.job_title }}</td></tr>{% endfor %}</table>in the python:employee_info=[{'name':'Sam','job':'programmer'},{'name':'Steve','job':'soda jerk'}]html=render(request,"employee_list.djt",{'employee_info':employee_info})print(html)output:<table><tr><th>Name</th><th>Job</th></tr><tr><td>Sam</td><td>programmer</td></tr><tr><td>Steve</td><td>soda jerk</td></tr></table>That's great - but why have the loop inside the template? If the table row is going to be repeated an arbitrary number of times, doesn't it make sense that this row should have its own template? In the Template-Nest scheme, this would look like:table.html:<table><tr><th>Name</th><th>Job</th></tr><!--% rows %--></table>table_row.html:<tr><td><!--% name %--></td><td><!--% job %--></td></tr>and in the Python:table={'NAME':'table','rows':[{'NAME':'table_row','name':'Sam','job':'programmer'},{'NAME':'table_row','name':'Steve','job':'soda jerk'}]}nest=Nest({'token_delims':['<!--%','%-->']})print(nest.render(table))Now the processing is entirely in the Python. Of course, if you need to fill in your table rows using a loop, this is easy:rows=[]foritemindata:rows.append({'NAME':'table_row','name':item.name,'job':item.job})table={'NAME':'table','rows':'rows'}nest=Nest({'token_delims':['<!--%','%-->']})printnest.render(table)Template-Nest is far simpler, and makes far more sense!METHODSconstructornest=Nest(args)args is a dict that can contain keys corresponding to any of the methods Template-Nest accepts. For example you can do:nest=Nest({template_dir=>'/my/template/dir'})or equally:nest=Nest()nest.template_dir('/my/template/dir')comment_delimsUse this in conjunction with show_labels. Get/set the delimiters used to define comment labels. Expects a 2 element list. E.g. if you were templating javascript you could do:nest.comment_delims(['/*','*/'])Now your output will have labels like/* BEGIN my_js_file */// your code/* END my_js_file */You can set the second comment token as an empty string if the language you are templating does not use one. E.g. for Python:nest.comment_delims(['#',''])defaultsProvide a dict of default values to have Template-Nest auto-fill matching parameters (no matter where they are found in the template tree). For example:box.html:<divclass='box'><!--% contents %--></div>link.html:<ahref="<!--% soup_website_url %-->">Soup of the day is<!--% todays_soup %-->!</a>in the Python:nest=Nest({'token_delims':['<!--%','%-->']})my$page={NAME=>'box',contents=>{NAME=>'link',todays_soup=>'French Onion Soup'}}html=nest.render(page)print(html)this prints:<divclass='box'><ahref="">Soup of the day is French Onion Soup !</a></div>Note the blank "href" value - because we didn't pass it as a default, or specify it explicitly.
Now lets set some defaults:nest.defaults({'soup_website_url':'http://www.example.com/soup-addicts','some_other_url':'http://www.example.com/some-other-url'#any default that doesn't appear})#in any template is simply ignoredhtml=nest.render(page)This time "href" is populated:<divclass='box'><ahref="http://www.example.com/soup-addicts">Soup of the day is French Onion Soup</a></div>Alternatively provide the value explicitly and override the default:page={'NAME':'box','contents':{'NAME':'link','todays_soup':'French Onion Soup','soup_website_url':'http://www.example.com/soup-url-override'}}html=nest.render(page)The result:<divclass='box'><ahref='http://www.example.com/soup-url-override'</div>ie.defaultsallows you to preload yournestwith any values which you expect to remain constant throughout your project.You can alsonamespaceyour default values. Say you think it's a better idea to differentiate parameters coming from config from those you are expecting to explicitly pass in. You can do something like this:link.html:<ahref="<--% config.soup_website_url %-->">Soup of the day is<!--% todays_soup %-->!</a>ie you are reserving theconfig.prefix for parameters you are expecting to come from the config. To set the defaults in this case you could do this:defaults={'config.soup_website_url'=>'http://www.example.com/soup-addicts','config.some_other_url'=>'http://www.example.com/some-other-url'#...}nest.defaults(defaults)but writing 'config.' repeatedly is a bit effortful, so Template-Nest allows you to do the following:defaults = {

        'config': {

            'soup_website_url': 'http://www.example.com/soup-addicts',
            'some_other_url': 'http://www.example.com/some-other-url'
    
            #...
        },

        'some_other_namespace': {

            # other params?

        }

    }

    nest.defaults( defaults )
    nest.defaults_namespace_char('.') # not actually necessary, as '.' is the defaultNow Template-Nest will replaceconfig.soup_website_urlwith what it finds indefaults[config][soup_website_url]Seedefaults_namespace_char.defaults_namespace_charAllows you to provide a "namespaced" defaults dict rather than just a flat one. ie instead of doing this:nest.defaults({'variable1':'value1','variable2':'value2',# ...})You can do this:nest.defaults({'namespace1':{'variable1':'value1','variable2':'value2'},'namespace2':{'variable1':'value3','variable2':'value4}})Specify yourdefaults_namespace_charto tell Template-Nest how to match these defaults in your template:nest.defaults_namespace_char('-')so now the token<% namespace1-variable1 %>will be replaced withvalue2. Note the defaultdefaults_namespace_charis a fullstop (period) character.error_on_bad_paramsIf you attempt to populate a template with a parameter that doesn't exist (ie the name is not found in the template) then this normally results in an error. This default behaviour is recommended in most circumstances as it guards against typos and sloppy code. However, there may be circumstances where you want processing to carry on regardless. In this case seterror_on_bad_paramstoFalse:nest.error_on_bad_params(False)escape_charOn rare occasions you may actually want to use the exact character string you are using for your token delimiters in one of your templates. e.g. say you are using token_delims[%and%], and you have this in your template:Hello [% name %],

        did you know we are using token delimiters [% and %] in our templates?

    lots of love
    RogerClearly in this case we are a bit stuck because Template-Nest is going to think[% and %]is a token to be replaced. Not to worry, we canescapethe opening token delimiter:Hello [% name %],

        did you know we are using token delimiters \[% and %] in our templates?

    lots of love
    RogerIn the output the backslash will be removed, and the[% and %]will get printed verbatim.escape_charis set to be a backslash by default. This means if you want an actual backslash to be printed, you would need a double backslash in your template.You can change the escape character if necessary:nest.escape_char('X')or you can turn it off completely if you are confident you'll never want to escape anything. Do so by passing in the empty string toescape_char:nest.escape_char('')fixed_indentIntended to improve readability when inspecting nested templates. Consider the following example:box.html:<divclass='box'><!--% contents %--></div>photo.html:<div><imgsrc='/some_image.jpg'></div>In the python:nest=Nest({token_delims=>['<!--%','%-->']})nest.render({'NAME':'box','contents':'image'})Output:<divclass='box'><div><imgsrc='/some_image.jpg'></div></div>Note the ugly indenting. In fact this is completely correct behaviour in terms of faithfully replacing the token<!--% contents %-->with thephoto.htmltemplate - the nested template starts exactly from where the token was placed, and each character is printed verbatim, including the new lines.However, a lot of the time we really want output that looks like this:<divclass='box'><div><imagesrc='/some_image.jpg'># the indent is maintained</div># for every line in the child</div># templateTo get this more readable output, then setfixed_indenttoTrue:nest.fixed_indent(True)Bear in mind that this will result in extra space characters being inserted into the output.template_labelThe default isTEMPLATE(all-caps, case-sensitive). Of course ifTEMPLATEis interpreted as the filename of the template, then you can't useTEMPLATEas one of the variables in your template. ie<% TEMPLATE %>will never get populated. If you really are adamant about needing to have a template variable calledTEMPLATE- or you have some other reason for wanting an alternative label point to your template filename, then you can settemplate_label:nest.name_label('MYLABEL')#and nowcomponent={'MYLABEL':'name_of_my_component'#...}renderConvert a template structure to output text. Expects a dict (or list) containing dicts/lists/plain text.e.g.widget.html:<divclass='widget'><h4>I am a widget</h4><div><!--% widget_body %--></div></div>widget_body.html:<div><div>I am the widget body!</div><div><!--% some_widget_property %--></div></div>widget={'NAME':'widget','widget_body':{'NAME':'widget_body','some_widget_property':'Totally useless widget'}}print(nest.render(widget))Output:<divclass='widget'><h4>I am a widget</h4><div><div><div>I am the widget body!</div><div>Totally useless widget</div></div></div></div>show_labelsGet/set the show_labels property. This is a boolean with defaultFalse. Setting this toTrueresults in adding comments to the output so you can identify which template output text came from. This is useful in development when you have many templates. E.g. addingnest.show_labels(True)to the example in the synopsis results in the following:<!-- BEGIN page --><html><head><style>div{padding:20px;margin:20px;background-color:yellow;}</style></head><body><!-- BEGIN box --><div>First nested box</div><!-- END box --><!-- BEGIN box --><div>Second nested box</div><!-- END box --></body></html><!-- END page -->What if you're not templating html, and you still want labels? Then you should setcomment_delimsto whatever is appropriate for the thing you are templating.template_dirGet/set the dir where Template-Nest looks for your templates. E.g.nest.template_dir('/my/template/dir')Now if I havecomponent={NAME=>'hello',#...}andtemplate_ext = '.html', we'll expect to find the template at/my/template/dir/hello.html.Note that if you have some kind of directory structure for your templates (ie they are not all in the same directory), you can do something like this:component={'NAME':'/my/component/location','contents':'some contents or other'}Template-Nest will then prependNAMEwithtemplate_dir, appendtemplate_extand look in that location for the file. So in our example iftemplate_dir = '/my/template/dir'andtemplate_ext = '.html'then the template file will be expected to exist at/my/template/dir/my/component/location.html.Of course if you want components to be nested arbitrarily, it might not make sense to contain them in a prescriptive directory structure.template_extGet/set the template extension. This is so you can save typing your template extension all the time if it's always the same. The default is.html- however, there is no reason why this templating system could not be used to construct any other type of file (or why you could not use another extension even if you were producing html). So e.g. if you are wanting to manipulate javascript files:nest.template_ext('.js')# thenjs_file={'NAME':'some_js_file'#...}So here Template-Nest will look intemplate_dirforsome_js_file.js.If you don't want to specify a particulartemplate_ext(presumably because files don't all have the same extension) - then you can donest.template_ext('')In this case you would need to have NAME point to the full filename. ienest.template_ext('')component={NAME=>'hello.html',#...}token_delimsGet/set the delimiters that define a token (to be replaced).token_delimsis a 2 element list - corresponding to the opening and closing delimiters. For examplenest.token_delims(['[%','%]'])would mean that Template-Nest would now recognise and interpolate tokens in the format[% token_name %]The default token_delims are the mason style delimiters<%and%>. Note that forHTMLthe token delimiters<!--%and%-->make a lot of sense, since they allow raw templates (ie that have not had values filled in) to render as goodHTML.Building a template-less platform with Template-NestRecently someone reviewing Template-Nest suggested it wasn't a good system because "you end up with thousands of tiny templates". After some consideration I realised not only is this claim incorrect, the truth is pretty much the opposite: while you do end up with small templates (surely a good thing), you don't end up with more of them. In fact, you ought to end up with substantially less.This is because once you've created the smallest units you expect to repeat, you then collect those together to create larger units - and so on. In other words you create a tree structure, which will tend to have the smallest repeating units at the bottom (the actual templates). You are in fact creating your pages with the minimal amount of repetition.At this point I began to wonder what you would end up with if you took this philosophy to its extreme. In other words you start with the smallest possible building blocks imaginable, and build your pages from there. The result is quite interesting. I am not sure if it is a purely academic exercise for now, as I haven't tried to build it. However, so far I've been able to think of a response to every objection I could dream up. So it remains for someone to tell me why they think the scheme I am about to propose is not a good idea.It goes like this: you can build any html page with the following 3 "templates":"standalone":<[% tag %][% attributes %]>"closing":<[% tag %][% attributes %]>[% contents %]</[% tag %]>"attribute":[% key %]="[% value %]"(note the single space character at the beginning of theattributetemplate.)The main purpose of coming up with this was as a "riducto ad absurdum" argument to respond to the "thousands of templates" claim: its clear from this that the finer-grained you make your templates, thelessof them you need.Then I started wondering if this is isn't just an academic idea, but would actually work practically, and might even be a considerably superior method of rendering html?The templates are so small, they barely qualify as templates. It would be just as easy to stick them in constants in your code (given html itself is not likely to change any time soon). Hence this is why I referred to this system as "template-less". We go down to such tiny granularity that we end up with repeating units that might as well just be supplied directly by code.Ridiculous, you think? Actually, due to the "tree effect" phenomenon I already mentioned, it seems really quite quick to get to page-sized output. Consider this:defcss_tag(href):returnnest.render({'NAME':'standalone','tag':'link','attributes':[{'NAME':'attribute''key':'href','value':href},{'NAME':'attribute''key':'href','value':href}]})defscript_tag(href):returnnest.render({'NAME':'closing','tag':'script','attributes':[{'NAME':'attribute','key':'type','value':'text/javascript'},{'NAME':'attribute','key':'src','value':href}})defsection_container(contents):returnnest.render({'NAME':'closing','tag':'div','attributes':{'NAME':'attribute','key':'class','value':'section'},'contents':contents}))defpage_header(title):returnnest.render({'NAME':'closing','tag':'header','contents':[css_tag('/css/local.css'),css_tag('/css/third_party.css'),script_tag(/js/local.js'),script_tag(/js/third_party.js'),{'NAME':'closing','contents':title}]})defpage(title,contents,footer):returnnest.render([page_header(title),{'NAME':'closing','tag':'body','contents':[{section_container(contents),section_container(footer)}]}])# etc...So in this scheme you have functions that hand you the individual tags. Then functions that call those functions... and so on. Pretty quickly you have functions which produce much larger scale content. (In fact, doesn't the size of output increase exponentially with each nested function call? Say your first function produces a single tag, and the second invokes a set of those tags. The third produces a set of sets. The fourth...)What you've really done is swap out flat relatively immutable text files for dynamic, modifiable functions. Take for example thescript_tagfunction above. That function is now going to get called for every single script tag on the page. Want to changetext/javascriptto something else? You can do it in a second in our template-less system. Can you do that with large flat templates? It's going to be a major search and replace job.Actually you can intervene and make modifications at any point in the tree structure, with a change made only in one place, so that no more or less than the desired elements are affected. It is DRY taken to it's logical conclusion.Objections?OverheadThe main objection that immediately sprung to my mind is processing overhead. "Surely you don't seriously want to build large pages by individually rendering each and every single element? Those bottom level functions are going to get called an insane number of times!"That's true, but what about if we introduce caching? Suppose we create some kind of wrapper around those functions to return a cached value if we've seen the input before? So for example, the first time you callcss_tagwithhref = "example.com/css/my.css"it actually performs the render, but the second time it finds the value corresponding toexample.com/css/my.cssin the cache, and delivers the chunk of html directly? That cuts the function call chain short, and delivers the html without hitting the lower level functions at all. Actually, isn't this fantastically efficient? Now you're only going to render those specific chunks you've never seen before.(Ok - I guess there are going to be limits to how much you can cache, but still...)"You end up with a lot of functions!"I don't actually think this is going to be any kind of issue, but I'm trying to imagine what an objector might say. Yes, I think you'll have more functions - you'll probably have an extra module or so full of those low level functions that deliver individual tags. But so what?"It's difficult to visualise page output."If you like looking at files in a text editor, then this is true. However, it would be quite easy to have some kind of test system which meant you could call any individual function and look at the output. Potentially you could have it render in "debug" mode so it inserted comments saying which function each chunk came from (similar to Template-Nest'sshow_commentsmethod....?I don't know! Tell me why this wouldn't work? If you can't, maybe I'll try and build it...COPYRIGHT AND LICENSECopyright © 2019 by Tom GraceyThis library is free software; you can redistribute it and/or modify
it under the MIT License.