Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/language/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ POML supports various JavaScript expressions within the double curly brackets. T

The `<let>` tag allows you to define variables, import data from external files, and set values within your POML template.

To provide a fallback value that only applies when a variable hasn't been set yet, add `default="true"` to the `<let>` element. When used without a `name`, only properties that are not already defined will be merged into the context.

```xml
<poml>
<let name="greeting" value="'Hello'" />
<let name="greeting" value="'Hi'" default="true" />
<p>{{greeting}}</p> <!-- Outputs: Hello -->
</poml>
```

### Syntax 1: Setting a variable from a value

```xml
Expand Down
44 changes: 33 additions & 11 deletions packages/poml/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -510,10 +510,14 @@ export class PomlFile {
const type = xmlAttribute(element, 'type')?.value;
const name = xmlAttribute(element, 'name')?.value;
const value = xmlAttribute(element, 'value')?.value;
const isDefault = xmlAttribute(element, 'default')?.value?.toLowerCase() === 'true';

// Case 1: <let name="var1" src="/path/to/file" />, case insensitive
// or <let src="/path/to/file" />, case insensitive
if (source) {
if (name && isDefault && Object.prototype.hasOwnProperty.call(contextOut, name)) {
return true;
}
let content: any;
try {
content = readSource(
Expand All @@ -531,14 +535,22 @@ export class PomlFile {
}
if (!name) {
if (content && typeof content === 'object') {
Object.assign(contextOut, content);
if (isDefault) {
for (const [key, value] of Object.entries(content)) {
if (!Object.prototype.hasOwnProperty.call(contextOut, key)) {
contextOut[key] = value;
}
}
} else {
Object.assign(contextOut, content);
}
} else {
this.reportError(
'name attribute is expected when the source is not an object.',
this.xmlElementRange(element),
);
}
} else {
} else if (!(isDefault && Object.prototype.hasOwnProperty.call(contextOut, name))) {
contextOut[name] = content;
}
return true;
Expand All @@ -553,13 +565,15 @@ export class PomlFile {
);
return true;
}
const evaluated = this.evaluateExpression(
value,
contextIn,
this.xmlAttributeValueRange(xmlAttribute(element, 'value')!),
true,
);
contextOut[name] = evaluated;
if (!(isDefault && Object.prototype.hasOwnProperty.call(contextOut, name))) {
const evaluated = this.evaluateExpression(
value,
contextIn,
this.xmlAttributeValueRange(xmlAttribute(element, 'value')!),
true,
);
contextOut[name] = evaluated;
}
return true;
}

Expand All @@ -582,14 +596,22 @@ export class PomlFile {
}
if (!name) {
if (content && typeof content === 'object') {
Object.assign(contextOut, content);
if (isDefault) {
for (const [key, value] of Object.entries(content)) {
if (!Object.prototype.hasOwnProperty.call(contextOut, key)) {
contextOut[key] = value;
}
}
} else {
Object.assign(contextOut, content);
}
} else {
this.reportError(
'name attribute is expected when the source is not an object.',
this.xmlElementRange(element),
);
}
} else {
} else if (!(isDefault && Object.prototype.hasOwnProperty.call(contextOut, name))) {
contextOut[name] = content;
}

Expand Down
21 changes: 21 additions & 0 deletions packages/poml/tests/file.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,27 @@ describe('templateEngine', () => {
expect(await poml(text)).toBe('2\n\n4');
expect(ErrorCollection.empty()).toBe(true);
});

test('letDefaultSetsWhenUnset', async () => {
const text = '<let name="greeting" value="\'Hi\'" default="true" /><p>{{greeting}}</p>';
expect(await poml(text)).toBe('Hi');
expect(ErrorCollection.empty()).toBe(true);
});

test('letDefaultDoesNotOverride', async () => {
const text =
'<let name="greeting" value="\'Hello\'" />' +
'<let name="greeting" value="\'Hi\'" default="true" />' +
'<p>{{greeting}}</p>';
expect(await poml(text)).toBe('Hello');
expect(ErrorCollection.empty()).toBe(true);
});

test('letDefaultObjectMerge', async () => {
const text = '<let>{ "a": 1 }</let><let default="true">{ "a": 2, "b": 3 }</let><p>{{a}} {{b}}</p>';
expect(await poml(text)).toBe('1 3');
expect(ErrorCollection.empty()).toBe(true);
});
});

describe('expressionEvaluation', () => {
Expand Down
Loading