Add support for pipes in templates #53

The goal is to be able to modify values of variables used in templates.
It enables future functionality such as escaping, inlining etc.

It adds support applying predefined pipes to variables. Pipes
can be applied to variable substitution in with and parameter
substitution expressions. They work in similar way to piping in Unix
where each pipe applied to the compiled result of pipe before.

It adds support for using pipes in `with` and parameter substitution
expressions. It also refactors how their regex is build to reuse more of
the logic by abstracting regex building into a new class.

Finally, it separates and extends documentation for templating.
This commit is contained in:
undergroundwires
2021-09-08 18:58:30 +01:00
parent 862914b06e
commit 4d7ff7edc5
30 changed files with 1112 additions and 207 deletions

View File

@@ -16,8 +16,8 @@ describe('WithParser', () => {
},
{
name: 'when scope is used',
code: 'used here ({{ with $parameter }}value: {{ . }}{{ end }})',
expected: [ new ExpressionPosition(11, 55) ],
code: 'used here ({{ with $parameter }}value: {{.}}{{ end }})',
expected: [ new ExpressionPosition(11, 53) ],
},
{
name: 'when used twice',
@@ -25,30 +25,121 @@ describe('WithParser', () => {
expected: [ new ExpressionPosition(7, 51), new ExpressionPosition(61, 99) ],
},
{
name: 'tolerates lack of spaces around brackets',
code: 'no whitespaces {{with $parameter}}value: {{.}}{{end}}',
expected: [ new ExpressionPosition(15, 53) ],
},
{
name: 'does not tolerate space after dollar sign',
code: 'used here ({{ with $ parameter }}value: {{ . }}{{ end }})',
expected: [ ],
name: 'tolerate lack of whitespaces',
code: 'no whitespaces {{with $parameter}}value: {{ . }}{{end}}',
expected: [ new ExpressionPosition(15, 55) ],
},
);
});
describe('ignores when syntax is unexpected', () => {
runner.expectPosition(
{
name: 'does not tolerate whitespace after with',
code: '{{with $ parameter}}value: {{ . }}{{ end }}',
expected: [ ],
},
{
name: 'does not tolerate whitespace before dollar',
code: '{{ with$parameter}}value: {{ . }}{{ end }}',
expected: [ ],
},
);
describe('ignores when syntax is wrong', () => {
describe('ignores expression if "with" syntax is wrong', () => {
runner.expectPosition(
{
name: 'does not tolerate whitespace after with',
code: '{{with $ parameter}}value: {{ . }}{{ end }}',
expected: [ ],
},
{
name: 'does not tolerate whitespace before dollar',
code: '{{ with$parameter}}value: {{ . }}{{ end }}',
expected: [ ],
},
{
name: 'wrong text at scope end',
code: '{{ with$parameter}}value: {{ . }}{{ fin }}',
expected: [ ],
},
{
name: 'wrong text at expression start',
code: '{{ when $parameter}}value: {{ . }}{{ end }}',
expected: [ ],
},
);
});
describe('does not render argument if substitution syntax is wrong', () => {
runner.expectResults(
{
name: 'comma used instead of dot',
code: '{{ with $parameter }}Hello {{ , }}{{ end }}',
args: (args) => args
.withArgument('parameter', 'world!'),
expected: [ 'Hello {{ , }}' ],
},
{
name: 'single brackets instead of double',
code: '{{ with $parameter }}Hello { . }{{ end }}',
args: (args) => args
.withArgument('parameter', 'world!'),
expected: [ 'Hello { . }' ],
},
{
name: 'double dots instead of single',
code: '{{ with $parameter }}Hello {{ .. }}{{ end }}',
args: (args) => args
.withArgument('parameter', 'world!'),
expected: [ 'Hello {{ .. }}' ],
},
);
});
});
describe('renders scope conditionally', () => {
describe('does not render scope if argument is undefined', () => {
runner.expectResults(
{
name: 'does not render when value is undefined',
code: '{{ with $parameter }}dark{{ end }} ',
args: (args) => args
.withArgument('parameter', undefined),
expected: [ '' ],
},
{
name: 'does not render when value is empty',
code: '{{ with $parameter }}dark {{.}}{{ end }}',
args: (args) => args
.withArgument('parameter', ''),
expected: [ '' ],
},
{
name: 'does not render when argument is not provided',
code: '{{ with $parameter }}dark{{ end }}',
args: (args) => args,
expected: [ '' ],
},
);
});
describe('render scope when variable has value', () => {
runner.expectResults(
{
name: 'renders scope even if value is not used',
code: '{{ with $parameter }}Hello world!{{ end }}',
args: (args) => args
.withArgument('parameter', 'Hello'),
expected: [ 'Hello world!' ],
},
{
name: 'renders value when it has value',
code: '{{ with $parameter }}{{ . }} world!{{ end }}',
args: (args) => args
.withArgument('parameter', 'Hello'),
expected: [ 'Hello world!' ],
},
{
name: 'renders value when whitespaces around brackets are missing',
code: '{{ with $parameter }}{{.}} world!{{ end }}',
args: (args) => args
.withArgument('parameter', 'Hello'),
expected: [ 'Hello world!' ],
},
{
name: 'renders value multiple times when it\'s used multiple times',
code: '{{ with $letterL }}He{{ . }}{{ . }}o wor{{ . }}d!{{ end }}',
args: (args) => args
.withArgument('letterL', 'l'),
expected: [ 'Hello world!' ],
},
);
});
});
describe('ignores trailing and leading whitespaces and newlines inside scope', () => {
runner.expectResults(
@@ -82,60 +173,11 @@ describe('WithParser', () => {
},
);
});
describe('does not render scope if argument is undefined', () => {
runner.expectResults(
{
name: 'does not render when value is undefined',
code: '{{ with $parameter }}dark{{ end }} ',
args: (args) => args
.withArgument('parameter', undefined),
expected: [ '' ],
},
{
name: 'does not render when value is empty',
code: '{{ with $parameter }}dark {{.}}{{ end }}',
args: (args) => args
.withArgument('parameter', ''),
expected: [ '' ],
},
{
name: 'does not render when argument is not provided',
code: '{{ with $parameter }}dark{{ end }}',
args: (args) => args,
expected: [ '' ],
},
);
});
describe('renders scope as expected', () => {
runner.expectResults(
{
name: 'renders scope even if value is not used',
code: '{{ with $parameter }}Hello world!{{ end }}',
args: (args) => args
.withArgument('parameter', 'Hello'),
expected: [ 'Hello world!' ],
},
{
name: 'renders value when it has value',
code: '{{ with $parameter }}{{ . }} world!{{ end }}',
args: (args) => args
.withArgument('parameter', 'Hello'),
expected: [ 'Hello world!' ],
},
{
name: 'renders value when whitespaces around brackets are missing',
code: '{{ with $parameter }}{{.}} world!{{ end }}',
args: (args) => args
.withArgument('parameter', 'Hello'),
expected: [ 'Hello world!' ],
},
{
name: 'renders value multiple times when it\'s used multiple times',
code: '{{ with $letterL }}He{{ . }}{{ . }}o wor{{ . }}d!{{ end }}',
args: (args) => args
.withArgument('letterL', 'l'),
expected: [ 'Hello world!' ],
},
);
describe('compiles pipes in scope as expected', () => {
runner.expectPipeHits({
codeBuilder: (pipeline) => `{{ with $argument }} {{ .${pipeline}}} {{ end }}`,
parameterName: 'argument',
parameterValue: 'value',
});
});
});