一个纯文本文件模板引擎的想法

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