I sometimes need to generate text files. The cases so far were plain/text emails and configuration files. In the former case you must be precise, you can't get away with, "you know, those are generated, they're still readable even if there are extra spaces here and there." In the latter you can, but having a readable result is nice to have.
Now, how do I make the result precise (in terms of whitespace), and the template readable as well? More readable than what follows.
With jinja
I can do this:
{%- if order.name -%}
Name: {{ order.name + '\n' -}}
{%- endif -%}
Phone: {{ order.phone + '\n' -}}
{%- if order.branch or order.address -%}
{{- '\n' -}}
{%- if order.branch -%}
Branch: {{ order.branch + '\n' -}}
{%- endif -%}
{%- if order.address -%}
Address: {{ order.address + '\n' -}}
{%- endif -%}
{%- endif -%}
{%- for pvxo in order.productvariantxorders.all() -%}
{{- '\n' -}}
Product: {{ (pvxo.productvariant.product.name
if pvxo.productvariant
else pvxo.product.name) + '\n' -}}
{%- endfor -%}
Which might produce:
Name: ...
Phone: ...
Address: ...
Product: ...
Product: ...
But it took me a while to figure out how to make jinja
behave this way.
With Javascript (_.template
):
const _ = require('lodash');
const tpl =
'<% if (order.name) { %>'
+ 'Name: <%= order.name %>\n'
+ '<% } %>'
+ 'Phone: <%= order.phone %>\n'
+ '<% if (order.branch || order.address) { %>'
+ '\n'
+ '<% if (order.branch) { %>'
+ 'Branch: <%= order.branch %>\n'
+ '<% } %>'
+ '<% if (order.address) { %>'
+ 'Address: <%= order.address %>\n'
+ '<% } %>'
+ '<% } %>'
+ '<% order.productvariantxorders.forEach(pvxo => { %>'
+ '\n'
+ 'Product: <%= pvxo.productvariant'
+ ' ? pvxo.productvariant.product.name'
+ ' : pvxo.product.name %>\n'
+ '<% }) %>';
console.log(_.template(tpl)({
order: {
name: '...',
phone: '...',
address: '...',
productvariantxorders: [
{productvariant: {product: {name: '...'}}},
{product: {name: '...'}},
],
}
}));
Which is more straightforward.
And these 2 doesn't cover the case where you need indents. Still those are the best solutions I could come up with. It doesn't seem like there are many options. The question is language-agnostic. You can point to some particular template engine that provides for a better outcome. Or just suggest an idea of such syntax.
On a side note, here's a somewhat related question I inspected for an answer.
Note. I wish I found something better, but I failed.
For now I settled with the Javascript solution because it's simpler to understand (more straightforward). An example of a template:
module.exports =
'server {\n'
+ ' server_name'
+ '<% domains.forEach(d => { %>'
+ ' <%= d %>'
+ '<% }) %>'
+ ';\n'
+ ' location / {\n'
+ ' return 301 https://<%= domains[0] %>$request_uri;\n'
+ ' }\n'
+ ' location /.well-known {\n'
+ ' root <%= ssl_docroot %>;\n'
+ ' }\n'
+ '}\n'
+ '\n'
+ 'server {\n'
+ ' server_name'
+ '<% domains.forEach(d => { %>'
+ ' <%= d %>'
+ '<% }) %>'
+ ';\n'
+ ' listen 443 ssl;\n'
+ '<% const d = (simp_le ? "/etc/certs/" : "/etc/letsencrypt/live/") + domains[0] %>'
+ ' ssl_certificate <%= d %>/fullchain.pem;\n'
+ '<% const f = simp_le ? "key.pem" : "privkey.pem" %>'
+ ' ssl_certificate_key <%= d %>/<%= f %>;\n'
+ '\n'
+ ' access_log /var/log/nginx/<%= domains[0] %>-access.log;\n'
+ ' error_log /var/log/nginx/<%= domains[0] %>-error.log;\n'
+ '\n'
+ ' client_max_body_size 50m;\n'
+ '\n'
+ ' # https://raw.githubusercontent.com/h5bp/server-configs-nginx/3.3.0/h5bp/web_performance/compression.conf\n'
+ ' gzip on;\n'
+ ' gzip_comp_level 5;\n'
+ ' gzip_min_length 256;\n'
+ ' gzip_proxied any;\n'
+ ' gzip_vary on;\n'
+ ' gzip_types\n'
+ ' application/atom+xml\n'
+ ' application/geo+json\n'
+ ' application/javascript\n'
+ ' application/x-javascript\n'
+ ' application/json\n'
+ ' application/ld+json\n'
+ ' application/manifest+json\n'
+ ' application/rdf+xml\n'
+ ' application/rss+xml\n'
+ ' application/vnd.ms-fontobject\n'
+ ' application/wasm\n'
+ ' application/x-web-app-manifest+json\n'
+ ' application/xhtml+xml\n'
+ ' application/xml\n'
+ ' font/eot\n'
+ ' font/otf\n'
+ ' font/ttf\n'
+ ' image/bmp\n'
+ ' image/svg+xml\n'
+ ' text/cache-manifest\n'
+ ' text/calendar\n'
+ ' text/css\n'
+ ' text/javascript\n'
+ ' text/markdown\n'
+ ' text/plain\n'
+ ' text/xml\n'
+ ' text/vcard\n'
+ ' text/vnd.rim.location.xloc\n'
+ ' text/vtt\n'
+ ' text/x-component\n'
+ ' text/x-cross-domain-policy;\n'
+ '\n'
+ ' charset utf-8;\n'
+ '\n'
+ ' location / {\n'
+ ' proxy_pass http://<%= lxc_ip %>;\n'
+ ' proxy_set_header Host $http_host;\n'
+ ' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n'
+ ' proxy_set_header X-Forwarded-Proto https;\n'
+ ' }\n'
+ '<% if (websockets) { %>'
+ '\n'
+ ' location /ws {\n'
+ ' proxy_pass http://<%= lxc_ip %>;\n'
+ ' proxy_set_header Host $http_host;\n'
+ ' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n'
+ ' proxy_set_header X-Forwarded-Proto https;\n'
+ '\n'
+ ' proxy_http_version 1.1;\n'
+ ' proxy_set_header Upgrade $http_upgrade;\n'
+ ' proxy_set_header Connection Upgrade;\n'
+ ' }\n'
+ '<% } %>'
+ '\n'
+ ' location /.well-known {\n'
+ ' root <%= ssl_docroot %>;\n'
+ ' }\n'
+ '}\n';
It can be used from a sh
script like so:
cat <<CODE | node - "$tpl" "$vars"
const _ = require('lodash');
const tpl = require('./files/' + process.argv[2] + '.tpl');
const vars = JSON.parse(process.argv[3]);
console.log(_.template(tpl)(vars));
CODE