{ "info": { "author": "Tom Gracey", "author_email": "tomgracey@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6" ], "description": "# Template-Nest Templating System\n\n## TL;DR\nYou should definitely use Template-Nest because it's the most logical templating system in existence. \n\n* Create templates that have no code in them whatsoever. No \"template ifs\", \"template loops\". No \"template includes\". Nothing but holes to fill in.\n* No code in your templates means they are language independent. Use the same templates with Java, Ruby, Perl, Javascript...\n* If templating html, produce only ordinary html files that can be displayed independently in a browser. (ie. no files with weird extensions)\n* Build views as a tree structure of templates (exactly as the DOM does!) limiting the number of templates needed and avoiding repetition.\n* There is no \"template language\" to use or learn\n* Eliminate the confusion of where to put processing. Get rid of the mess this confusion produces.\n\nFor intrigue, also see: \"Building a template-less platform with Template-Nest\" at the bottom of this page.\n\n## SYNOPSIS\n\n~~~html\n\t\n\t\n\t\t\n\t\t\t\n\t\t\n\n\t\t\n\t\t\t<% contents %>\n\t\t\n\t\n~~~ \n\n```html\n\t\n\t
\n\t\t<% title %>\n\t
\n```\n\n```python\n\n from template_nest import Nest\n\n\tnest = Nest({\n\t\ttemplate_dir => '/html/templates/dir',\n fixed_indent => 1\n\t})\n\n\tpage = {\n\t\t'NAME': 'page',\n\t\t'contents': [{\n\t\t\tNAME => 'box',\n\t\t\ttitle => 'First nested box'\n\t\t}]\n\t}\n\n\tpage[ 'contents' ].append({\n\t\tNAME => 'box',\n\t\ttitle => 'Second nested box'\n\t})\n\n\tprint( nest.render( page ) )\n```\n\t\n### Output:\n\n```html\n \n\t \n\t\t \n\t \n\n\t \t \n
\n\t First nested box\n
\n
\n\t Second nested box\n
\n\t \n \n```\n\n\n\n\n## In depth\n\nSo 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.\n\nI originally developed Template-Nest in Perl (see [here](https://metacpan.org/pod/Template::Nest). 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!)\n\nI decided to port it over to Python for several reasons:\n\n* I want to use the same templates with a Django project that are already being used in a Perl project. (The most practical reason).\n* I think this is a great first module to get my feet wet publishing on PyPI. (The most humble reason).\n* I want this templating philosophy to take over the world. (The most ambitious reason!)\n\nSeriously 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.\n\nThis 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.\n\n# So what's the problem?\n\nI think there are actually 2 problems:\n\n1. Everyone is busily stuffing their \"templates\" full of code. I believe this\n\n* is not a good idea\n* violates MVC (sorry, but you haven't actually separated control from view)\n* means you probably need to learn a new mini language (the \"templating language\") on top of Python and/or whatever else.\n* makes displaying your raw template (e.g. in a browser) difficult (the browser can't handle `if`,`then` etc. statements written in your \"templating language\"\n* gives you an awkward file type with an uncomfortable extension (e.g. see [this debate](https://stackoverflow.com/questions/2035362/django-template-file-extension-preference) on the confusion of what extension is best to use for django templates.)\n* makes editing your raw template difficult (you have a mixture of markup and code, so which syntax highlighter do you pick?)\n* 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 an `if`,`then` structure, 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.\n\n2. Most templating systems produce *tightly coupled* templates\n\nThis is an issue I wasn't even really aware of until I discovered Template-Nest **doesn't** do this, and then noticed how much better it was without the coupling. So what do I mean by *tightly coupled*? Let's take Django. The [Tutorials Point documentation on Django templates](https://www.tutorialspoint.com/django/django_template_system.htm) helpfully 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! You *can* design a templating language without template inheritance. Template-Nest is one such templating system!\n\nBasically most templating systems encourage you to refer to templates from other templates. This might be done with an \"include\" - something like this:\n\n~~~html\n \n\n
Main Page Title
\n\n
{% include content.djt %}
\n\n
Some Footer
\n~~~\n\nor with an \"extend\", like this:\n\n~~~html\n \n {% extends parent.djt %}\n\n
Some Childish Stuff
\n\n~~~\n\nIn either case, you are essentially hard coding the relationship between your templates *inside* the templates. E.g. what happens if one time you want to include `other_content.djt` instead of `content.djt` in `page.djt` above? Yes you can do it with inheritance, but it's ugly and you are going to end up with one more template. \n\nThe point being, in my opinion the relationship between templates is *also 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 template `include` and `extends` should also not be necessary.\n\n# How is Template-Nest different?\n\nI 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.\n\nWith 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.\n\n\n\n## AN EXAMPLE\n\nLets 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:\n\n### in letter.html\n\n```\n\n {% include address.djt %}\n\n Dear {{username}}\n\n ....\n```\n\n\nHowever, in Template-Nest there's no `include` - there are only tokens to fill in, so you would have\n\n### in letter.html:\n\n```html\n \n\n Dear \n\n ...\n```\n\nI specify that I want to use `address.html` when I fill out the template, thus:\n\n```python\n letter = {\n 'NAME': 'letter', # this specifies \"letter.html\" (provided template_ext=\".html\")\n 'username': 'billy', \n 'address': {\n 'NAME': 'address', # \"address.html\" \n \n # any other variables in 'address.html'\n }\n }\n\n print( nest.render( letter ) )\n```\n\n\nThis is much better, because now `letter.html` is not hard-coded to use `address.html`. You can decide to use a different address template without needing to change the letter template.\n\nCommonly used template structures can be labelled (`main_page` etc.) stored in your code in functions, dicts, object attributes or whatever method seems the most convenient.\n\n## Another example\n\nThe 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: \n\n### in employee_list.djt:\n\n```html\n \n \n \n \n\n {% for employee in employees %}\n \n \n \n \n {% endfor %}\n
NameJob
{{ employee.name }}{{ employee.job_title }}
\n```\n\n### in the python:\n\n```python\n\n employee_info = [\n {'name': 'Sam', 'job': 'programmer'}, \n {'name': 'Steve', 'job': 'soda jerk'}\n ]\n html = render( request, \"employee_list.djt\", { 'employee_info' : employee_info })\n\n print( html )\n```\n\n### output:\n```html\n \n \n \n \n \n \n \n \n \n \n
NameJob
Samprogrammer
Stevesoda jerk
\n```\n\n\nThat'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:\n\n### table.html:\n\n```html\n \n \n \n \n\n \n\n
NameJob
\n```\n\n### table_row.html:\n\n```html\n \n \n \n \n```\n\n### and in the Python:\n\n```python\n\n table = {\n 'NAME': 'table',\n 'rows': [{\n 'NAME': 'table_row',\n 'name': 'Sam',\n 'job': 'programmer'\n }, {\n 'NAME': 'table_row',\n 'name': 'Steve',\n 'job': 'soda jerk'\n }]\n }\n\n nest = Nest({\n 'token_delims': ['']\n })\n\n print( nest.render( table ) )\n```\n\nNow the processing is entirely in the Python. Of course, if you need to fill in your table rows using a loop, this is easy:\n\n```python\n rows = []\n\n for item in data:\n\n rows.append({\n 'NAME': 'table_row',\n 'name': item.name,\n 'job': item.job\n })\n\n\n table = {\n\n 'NAME': 'table',\n 'rows': 'rows'\n\n }\n\n nest = Nest({\n 'token_delims': ['']\n })\n\n print nest.render( table )\n```\n\nTemplate-Nest is far simpler, and makes far more sense!\n\n\n\n\n## METHODS\n\n\n### constructor\n\n```python\n nest = Nest( args )\n```\n\nargs is a dict that can contain keys corresponding to any of the methods Template-Nest accepts. For example you can do:\n\n```python\n nest = Nest({ template_dir => '/my/template/dir' })\n```\n\nor equally:\n\n```python\n nest = Nest()\n nest.template_dir( '/my/template/dir' )\n```\n\n\n### comment_delims\n\nUse 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:\n\n```python\n\n nest.comment_delims( ['/*', '*/'] )\n\n```\n \nNow your output will have labels like\n\n```javascript\n\n /* BEGIN my_js_file */\n\n // your code\n\n /* END my_js_file */\n\n```\n\nYou can set the second comment token as an empty string if the language you are templating does not use one. E.g. for Python:\n\n```python\n\n nest.comment_delims([ '#','' ])\n\n```\n\n### defaults\n\nProvide 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:\n\n\n### box.html:\n\n```html\n\n
\n \n
\n\n```\n\n### link.html:\n\n```html\n\n \">Soup of the day is !\n\n```\n\n### in the Python:\n\n```python\n\n nest = Nest({\n 'token_delims': ['']\n })\n\n my $page = {\n NAME => 'box',\n contents => {\n NAME => 'link',\n todays_soup => 'French Onion Soup'\n }\n }\n\n html = nest.render( page )\n\n print( html )\n\n```\n\n### this prints:\n \n```html\n\n
\n Soup of the day is French Onion Soup !\n
\n\n```\n\nNote the blank \"href\" value - because we didn't pass it as a default, or specify it explicitly.\nNow lets set some defaults:\n\n```python\n\n nest.defaults({\n 'soup_website_url': 'http://www.example.com/soup-addicts',\n 'some_other_url': 'http://www.example.com/some-other-url' #any default that doesn't appear\n }) #in any template is simply ignored\n\n html = nest.render( page )\n```\n\nThis time \"href\" is populated:\n\n```html\n\n
\n Soup of the day is French Onion Soup\n
\n\n```\n\nAlternatively provide the value explicitly and override the default:\n\n```python\n\n page = {\n 'NAME': 'box',\n 'contents': {\n 'NAME': 'link',\n 'todays_soup': 'French Onion Soup',\n 'soup_website_url': 'http://www.example.com/soup-url-override'\n }\n }\n\n html = nest.render( page )\n\n```\n\nThe result:\n\n```html\n\n
\n \n\n```\n\nie. `defaults` allows you to preload your `nest` with any values which you expect to remain constant throughout your project.\n\n\nYou can also *namespace* your 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:\n\n### link.html:\n\n```html\n \">Soup of the day is !\n```\n\nie you are reserving the `config.` prefix for parameters you are expecting to come from the config. To set the defaults in this case you could do this:\n\n```python\n\n defaults = {\n 'config.soup_website_url' => 'http://www.example.com/soup-addicts',\n 'config.some_other_url' => 'http://www.example.com/some-other-url'\n \n #...\n }\n\n nest.defaults( defaults )\n\n```\n\nbut writing 'config.' repeatedly is a bit effortful, so Template-Nest allows you to do the following:\n\n\n```html\n\n defaults = {\n\n 'config': {\n\n 'soup_website_url': 'http://www.example.com/soup-addicts',\n 'some_other_url': 'http://www.example.com/some-other-url'\n \n #...\n },\n\n 'some_other_namespace': {\n\n # other params?\n\n }\n\n }\n\n nest.defaults( defaults )\n nest.defaults_namespace_char('.') # not actually necessary, as '.' is the default\n\n```\n\nNow Template-Nest will replace `config.soup_website_url` with what it finds in\n\n```python\n\n defaults[config][soup_website_url]\n\n```\n\nSee `defaults_namespace_char`.\n\n\n\n### defaults_namespace_char\n\nAllows you to provide a \"namespaced\" defaults dict rather than just a flat one. ie instead of doing this:\n\n```python\n\n nest.defaults({\n 'variable1': 'value1',\n 'variable2': 'value2',\n\n # ...\n\n })\n```\n\nYou can do this:\n\n\n```python\n\n nest.defaults({ \n 'namespace1': {\n 'variable1': 'value1',\n 'variable2': 'value2'\n },\n\n 'namespace2': {\n 'variable1': 'value3',\n 'variable2': 'value4\n }\n })\n```\n\nSpecify your `defaults_namespace_char` to tell Template-Nest how to match these defaults in your template:\n\n```python\n nest.defaults_namespace_char('-')\n```\n\nso now the token\n\n```\n <% namespace1-variable1 %>\n```\n\nwill be replaced with `value2`. Note the default `defaults_namespace_char` is a fullstop (period) character.\n\n\n### error_on_bad_params\n\nIf 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 set `error_on_bad_params` to `False`:\n\n```python\n nest.error_on_bad_params( False )\n```\n\n### escape_char\n\nOn 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:\n\n```\n Hello [% name %],\n\n did you know we are using token delimiters [% and %] in our templates?\n\n lots of love\n Roger\n```\n\nClearly 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 can *escape* the opening token delimiter:\n\n```\n Hello [% name %],\n\n did you know we are using token delimiters \\[% and %] in our templates?\n\n lots of love\n Roger\n```\n\nIn the output the backslash will be removed, and the `[% and %]` will get printed verbatim. \n\n`escape_char` is 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.\n\nYou can change the escape character if necessary:\n\n```python\n nest.escape_char('X')\n```\n\nor 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 to `escape_char`:\n\n```python\n nest.escape_char('')\n```\n\n\n### fixed_indent\n\nIntended to improve readability when inspecting nested templates. Consider the following example:\n\nbox.html:\n\n```html\n
\n \n
\n```\n\n\nphoto.html:\n\n```html\n
\n \n
\n```\n\nIn the python:\n\n\n```python\n nest = Nest({\n token_delims => ['']\n })\n \n nest.render({\n 'NAME': 'box',\n 'contents': 'image'\n })\n```\n\nOutput:\n\n```html\n
\n
\n \n
\n
\n```\n\nNote the ugly indenting. In fact this is completely correct behaviour in terms of faithfully replacing the token \n\n```html\n \n```\n\nwith the `photo.html` template - the nested template starts exactly from where the token was placed, and each character is printed verbatim, including the new lines.\n\nHowever, a lot of the time we really want output that looks like this:\n\n```html\n
\n
\n # the indent is maintained\n
# for every line in the child\n
# template\n```\n\nTo get this more readable output, then set `fixed_indent` to `True`:\n\n```python\n nest.fixed_indent( True )\n```\n\nBear in mind that this will result in extra space characters being inserted into the output.\n\n\n\n### name_label\n\nThe default is `NAME` (all-caps, case-sensitive). Of course if `NAME` is interpreted as the filename of the template, then you can't use `NAME` as one of the variables in your template. ie\n\n```\n <% NAME %>\n```\n\nwill never get populated. If you really are adamant about needing to have a template variable called `NAME` - or you have some other reason for wanting an alternative label point to your template filename, then you can set name_label:\n\n```python\n nest.name_label( 'MYLABEL' )\n\n #and now\n\n component = {\n 'MYLABEL': 'name_of_my_component'\n #...\n }\n```\n\n### render\n\nConvert a template structure to output text. Expects a dict (or list) containing dicts/lists/plain text.\n\ne.g.\n\nwidget.html:\n\n```html\n
\n

I am a widget

\n
\n \n
\n
\n```\n\n\nwidget_body.html:\n```html\n
\n
I am the widget body!
\n
\n
\n```\n\n```python\n widget = {\n 'NAME': 'widget',\n 'widget_body': {\n 'NAME': 'widget_body',\n 'some_widget_property': 'Totally useless widget'\n }\n }\n\n print( nest.render( widget ) )\n```\n\nOutput:\n\n```html\n
\n

I am a widget

\n
\n
\n
I am the widget body!
\n
Totally useless widget
\n
\n
\n
\n```\n\n\n### show_labels\n\nGet/set the show_labels property. This is a boolean with default `False`. Setting this to `True` results 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. adding \n\n```python\n nest.show_labels( True )\n```\n\nto the example in the synopsis results in the following:\n\n```html\n \n \n \n \n \n\n \n \n \n
\n First nested box\n
\n \n\n \n
\n Second nested box\n
\n \n\n \n \n \n```\n\nWhat if you're not templating html, and you still want labels? Then you should set `comment_delims` to whatever is appropriate for the thing you are templating.\n\n\n\n### template_dir\n\nGet/set the dir where Template-Nest looks for your templates. E.g.\n\n```python\n nest.template_dir( '/my/template/dir' )\n```\n\nNow if I have\n\n```python\n component = {\n NAME => 'hello',\n #...\n }\n```\n\nand `template_ext = '.html'`, we'll expect to find the template at `/my/template/dir/hello.html`.\n\n\nNote 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:\n\n```python\n component = {\n 'NAME': '/my/component/location',\n 'contents': 'some contents or other'\n }\n```\n\nTemplate-Nest will then prepend `NAME` with `template_dir`, append `template_ext` and look in that location for the file. So in our example if `template_dir = '/my/template/dir'` and `template_ext = '.html'` then the template file will be expected to exist at `/my/template/dir/my/component/location.html`.\n\nOf course if you want components to be nested arbitrarily, it might not make sense to contain them in a prescriptive directory structure. \n\n\n### template_ext\n\nGet/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:\n\n```python\n nest.template_ext('.js')\n\n # then\n\n js_file = {\n 'NAME': 'some_js_file'\n #...\n }\n```\n\nSo here Template-Nest will look in `template_dir` for `some_js_file.js`.\n\nIf you don't want to specify a particular `template_ext` (presumably because files don't all have the same extension) - then you can do\n\n```python\n nest.template_ext('')\n```\n\nIn this case you would need to have NAME point to the full filename. ie\n\n```python\n nest.template_ext('')\n\n component = {\n NAME => 'hello.html',\n #...\n }\n```\n\n\n### token_delims\n\nGet/set the delimiters that define a token (to be replaced). `token_delims` is a 2 element list - corresponding to the opening and closing delimiters. For example\n\n```python\n nest.token_delims( ['[%', '%]'] )\n```\n\nwould mean that Template-Nest would now recognise and interpolate tokens in the format\n\n```\n [% token_name %]\n```\n\nThe default token_delims are the mason style delimiters `<%` and `%>`. Note that for `HTML` the token delimiters `` make a lot of sense, since they allow raw templates (ie that have not had values filled in) to render as good `HTML`.\n\n\n## Building a template-less platform with Template-Nest\n\nRecently 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.\n\nThis 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.\n\nAt 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.\n\nIt goes like this: you can build any html page with the following 3 \"templates\":\n\n* \"standalone\":\n\n```\n<[% tag %][% attributes %]>\n```\n\n* \"closing\":\n\n```\n<[% tag %][% attributes %]>[% contents %]\n```\n\n\n* \"attribute\":\n\n```\n [% key %]=\"[% value %]\"\n```\n\n(note the single space character at the beginning of the `attribute` template.)\n\nThe 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, the *less* of them you need.\n\nThen 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?\n\nThe 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.\n\nRidiculous, 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:\n\n```python\ndef css_tag( href ):\n\n return nest.render({\n 'NAME': 'standalone',\n 'tag': 'link',\n 'attributes': [{\n 'NAME': 'attribute'\n 'key': 'href',\n 'value': href\n }, {\n 'NAME': 'attribute'\n 'key': 'href',\n 'value': href\n }]\n })\n\n\ndef script_tag( href ):\n\n return nest.render({\n 'NAME': 'closing',\n 'tag': 'script',\n 'attributes': [{\n 'NAME': 'attribute',\n 'key': 'type',\n 'value': 'text/javascript'\n }, {\n 'NAME': 'attribute',\n 'key': 'src',\n 'value': href\n }\n })\n\n\ndef section_container( contents ):\n return nest.render({\n 'NAME': 'closing',\n 'tag': 'div',\n 'attributes': {\n 'NAME': 'attribute',\n 'key': 'class',\n 'value': 'section'\n },\n 'contents': contents\n })\n)\n\ndef page_header( title ):\n\n return nest.render({\n 'NAME': 'closing',\n 'tag': 'header',\n 'contents': [\n css_tag('/css/local.css'),\n css_tag('/css/third_party.css'),\n script_tag(/js/local.js'),\n script_tag(/js/third_party.js'),\n { 'NAME': 'closing',\n 'contents': title\n } \n ]\n })\n \ndef page( title,contents,footer ):\n\n return nest.render([\n page_header(title),\n {\n 'NAME': 'closing',\n 'tag': 'body',\n 'contents': [{\n section_container( contents ),\n section_container( footer )\n }]\n }\n ])\n \n # etc...\n```\n\n\nSo 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...)\n\nWhat you've really done is swap out flat relatively immutable text files for dynamic, modifiable functions. Take for example the `script_tag` function above. That function is now going to get called for every single script tag on the page. Want to change `text/javascript` to 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.\n\nActually 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.\n\n### Objections?\n\n1. Overhead\n\n The 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!\"\n\n 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 call `css_tag` with `href = \"example.com/css/my.css\"` it actually performs the render, but the second time it finds the value corresponding to `example.com/css/my.css` in 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. \n\n (Ok - I guess there are going to be limits to how much you can cache, but still...)\n\n2. \"You end up with a lot of functions!\"\n\n 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?\n\n3. \"It's difficult to visualise page output.\"\n\n 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's `show_comments` method.\n\n4. ...?\n\n I don't know! Tell me why this wouldn't work? If you can't, maybe I'll try and build it...\n\n\n## COPYRIGHT AND LICENSE\n\nCopyright © 2019 by Tom Gracey\n\nThis library is free software; you can redistribute it and/or modify\nit under the MIT License.", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "template-nest", "package_url": "https://pypi.org/project/template-nest/", "platform": "", "project_url": "https://pypi.org/project/template-nest/", "project_urls": null, "release_url": "https://pypi.org/project/template-nest/1.0.0/", "requires_dist": null, "requires_python": "", "summary": "Represent a nested structure of templates in a dict", "version": "1.0.0" }, "last_serial": 5999959, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "cfd110666daed57982af1c899fc15584", "sha256": "121984358633634c0e635fb5acdf0dc6c1a35b6292bf817bbe1dca4cedec1957" }, "downloads": -1, "filename": "template-nest-1.0.0.tar.gz", "has_sig": false, "md5_digest": "cfd110666daed57982af1c899fc15584", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 36502, "upload_time": "2019-10-19T14:16:54", "url": "https://files.pythonhosted.org/packages/d2/ab/f0d95e176925cb9ee8078d9c381ffc4958a2f9db36051e254bbe285025ae/template-nest-1.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "cfd110666daed57982af1c899fc15584", "sha256": "121984358633634c0e635fb5acdf0dc6c1a35b6292bf817bbe1dca4cedec1957" }, "downloads": -1, "filename": "template-nest-1.0.0.tar.gz", "has_sig": false, "md5_digest": "cfd110666daed57982af1c899fc15584", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 36502, "upload_time": "2019-10-19T14:16:54", "url": "https://files.pythonhosted.org/packages/d2/ab/f0d95e176925cb9ee8078d9c381ffc4958a2f9db36051e254bbe285025ae/template-nest-1.0.0.tar.gz" } ] }