一个纯文本文件模板引擎的想法
An idea of a template engine for plain text files
我有时需要生成文本文件。到目前为止的案例是 plain/text 电子邮件和配置文件。在前一种情况下,你必须精确,你不能逃避,“你知道,那些是生成的,即使这里和那里有额外的空格,它们仍然是可读的。”在后者中你可以,但是有一个可读的结果是很好的。
现在,如何使结果精确(就空格而言),以及模板可读性好?比下面的内容更具可读性。
有了jinja
我可以做到:
{%- 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 -%}
可能产生:
Name: ...
Phone: ...
Address: ...
Product: ...
Product: ...
但我花了一段时间才弄清楚如何让 jinja
以这种方式表现。
与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: '...'}},
],
}
}));
哪个更直接。
而这 2 个不包括您需要缩进的情况。这些仍然是我能想到的最好的解决方案。似乎没有太多选择。这个问题与语言无关。您可以指向一些提供更好结果的特定模板引擎。或者只是提出这种语法的想法。
附带说明一下,这里是 somewhat related question 我检查过的答案。
注意。我希望我能找到更好的东西,但我失败了。
现在我选择了 Javascript 解决方案,因为它更容易理解(更直接)。模板示例:
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';
它可以在 sh
脚本中使用,如下所示:
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
我有时需要生成文本文件。到目前为止的案例是 plain/text 电子邮件和配置文件。在前一种情况下,你必须精确,你不能逃避,“你知道,那些是生成的,即使这里和那里有额外的空格,它们仍然是可读的。”在后者中你可以,但是有一个可读的结果是很好的。
现在,如何使结果精确(就空格而言),以及模板可读性好?比下面的内容更具可读性。
有了jinja
我可以做到:
{%- 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 -%}
可能产生:
Name: ...
Phone: ...
Address: ...
Product: ...
Product: ...
但我花了一段时间才弄清楚如何让 jinja
以这种方式表现。
与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: '...'}},
],
}
}));
哪个更直接。
而这 2 个不包括您需要缩进的情况。这些仍然是我能想到的最好的解决方案。似乎没有太多选择。这个问题与语言无关。您可以指向一些提供更好结果的特定模板引擎。或者只是提出这种语法的想法。
附带说明一下,这里是 somewhat related question 我检查过的答案。
注意。我希望我能找到更好的东西,但我失败了。
现在我选择了 Javascript 解决方案,因为它更容易理解(更直接)。模板示例:
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';
它可以在 sh
脚本中使用,如下所示:
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