Skip to content

Commit

Permalink
Allow dotted names in templates to select into sub-objects.
Browse files Browse the repository at this point in the history
Part of #50.
  • Loading branch information
jkomoros committed Jul 23, 2023
1 parent 61a4c0a commit f9f4023
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,8 @@ thrown. Variables named `_` will have their results ignored.
Template names can also have modifiers: `My name is {{name|default:'Alex'}}` would mean that if the var `name` is not provided, it will return `Alex`. Some modifiers expect arguments and some don't. If it expects a string argument, it should be wrapped in either `'` or `"`. You can chain multiple, e.g. `{{name|default:'Alex'|optional}}`.
Template names can also have a `.` in them, which selects into the sub object. For example, `My name is {{person.name}}`, given `{person:{name:'Alex'}}` would render `My name is Alex`.
Modifiers:
- `default:'value'` - if the var isn't provided, it will use the provided value. Must always be provided as a string value. It will be coerced into a different type if one of the type modifiers is used.
- `optional` (expects no arguments) - for template.extract, the pattern doesn't need to exist. If it doesn't, it will return the default value or skip the key if no default has been configured. Ignored for template.render().
Expand Down
10 changes: 5 additions & 5 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
} from 'zod';

import {
assertUnreachable
assertUnreachable, getObjectProperty, setObjectProperty
} from './util.js';

//Unknown is a stand-in for JSON-style output
Expand All @@ -13,7 +13,7 @@ type TemplateValueArray = TemplateVars[];

type TemplateValue = TemplateValueArray | TemplateValueLeaf;

const templateVarRegExp = new RegExp('^[a-zA-Z0-9-_]+$');
const templateVarRegExp = new RegExp('^[a-zA-Z0-9-_.]+$');

const templateVar = z.string().regex(templateVarRegExp);

Expand Down Expand Up @@ -369,7 +369,7 @@ const renderTemplatePiece = (piece : TemplatePart, vars : TemplateVars) : string
if (typeof piece == 'string') return piece;
if (piece.var == IGNORE_VAR) return '';
//It's a replacement.
const v = vars[piece.var];
const v = getObjectProperty(vars, piece.var);
if (v === undefined) {
if (piece.default === undefined) throw new Error(`Template had a placeholder for ${piece.var} but it did not exist in vars and no default was provided.`);
return piece.default;
Expand Down Expand Up @@ -399,7 +399,7 @@ const defaultForPieces = (pieces : TemplatePart[]) : TemplateVars => {
//in later.
if (piece.default == undefined) continue;
const converter = VALUE_EXTRACTORS[piece.type];
result[piece.var] = converter(piece.default);
setObjectProperty(result, piece.var, converter(piece.default));
}
return result;
};
Expand Down Expand Up @@ -459,7 +459,7 @@ const _extractForTemplate = (input : string, pieces : TemplatePart[], loop : boo
if (match == undefined || match == '') continue;
//If it's '_' then we're told to drop it on the floor.
if (v.var == IGNORE_VAR) continue;
result[v.var] = extractForPiece(match, v);
setObjectProperty(result, v.var, extractForPiece(match, v));
}
results.push(result);
}
Expand Down
73 changes: 73 additions & 0 deletions test/template/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,36 @@ describe('Template', () => {
assert.deepStrictEqual(actual, golden);
});

it('template that uses a dotted property name', () => {
const template = 'foo{{ a.b|int}}';
const input = {
a: {
b: 3
}
};
const t = new Template(template);
const actual = t.render(input);
const golden = 'foo3';
assert.deepStrictEqual(actual, golden);
});

it('template with a loop that uses a dotted property name', () => {
const template = '{{ @loop:bar}}foo{{ a.b|int}}{{@end}}';
const input = {
bar: [
{
a: {
b: 3
}
}
]
};
const t = new Template(template);
const actual = t.render(input);
const golden = 'foo3';
assert.deepStrictEqual(actual, golden);
});

});

describe('template.extract', () => {
Expand Down Expand Up @@ -629,6 +659,49 @@ describe('template.extract', () => {
assert.deepStrictEqual(actual, golden);
});

it('dotted property extract works', () => {
const template = 'foo{{ a.b|int}}';
const input = 'foo342';
const t = new Template(template);
const actual = t.extract(input);
const golden = {
a: {
b: 342
}
};
assert.deepStrictEqual(actual, golden);
});

it('dotted property extract works', () => {
const template = '{{ @loop:bar}}foo{{ a.b|int}}{{%end}}';
const input = 'foo342';
const t = new Template(template);
const actual = t.extract(input);
const golden = {
bar: [
{
a: {
b: 342
}
}
]
};
assert.deepStrictEqual(actual, golden);
});

it('dotted property with default extract works', () => {
const template = 'foo{{ a.b|default:"342"}}';
const input = 'foo';
const t = new Template(template);
const actual = t.extract(input);
const golden = {
a: {
b: '342'
}
};
assert.deepStrictEqual(actual, golden);
});

it('pattern modifier', () => {
const template = 'foo{{name|pattern:"\\d+?"}}';
const input = 'foo342';
Expand Down

0 comments on commit f9f4023

Please sign in to comment.