Skip to content

Commit

Permalink
feat(template): execute transformations when applying templates
Browse files Browse the repository at this point in the history
  • Loading branch information
danielo515 committed Dec 19, 2024
1 parent 2dbe84c commit 7552a2a
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 7 deletions.
7 changes: 4 additions & 3 deletions EXAMPLE_VAULT/.obsidian/plugins/modal-form/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,6 @@
],
"version": "1",
"template": {
"createCommand": true,
"parsedTemplate": [
{
"_tag": "text",
Expand Down Expand Up @@ -514,7 +513,8 @@
},
{
"_tag": "variable",
"value": "dateOfBirth"
"value": "dateOfBirth",
"transformation": "stringify"
},
{
"_tag": "text",
Expand Down Expand Up @@ -576,7 +576,8 @@
"_tag": "text",
"value": "\n\n---\n> Last modified: <% tp.file.last_modified_date(\"dddd, MMMM Do YYYY HH:mm:ss\") %>"
}
]
],
"createCommand": true
}
}
]
Expand Down
78 changes: 78 additions & 0 deletions src/core/template/templateParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,84 @@ describe("parseTemplate", () => {
]),
);
});

it("should properly execute a template with transformations", () => {
const template = "Hello, {{name|upper}}! You are {{age|stringify}} years old.";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: "John", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of("Hello, JOHN! You are 18 years old."));
});

it("Should execute a template with lowercase transformations", () => {
const template = "Hello, {{name|lower}}! You are {{age}} years old.";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: "John", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of("Hello, john! You are 18 years old."));
});

it("Should execute a template with trim transformations", () => {
const template = "Hello, {{name|trim}}!";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: " John ", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of("Hello, John!"));
});

it("Should execute a template with stringify transformations", () => {
const template = "Hello, {{name|stringify}}!";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: "John", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of('Hello, "John"!'));
});

it("Spaces around transformations should be ignored", () => {
const template = "Hello, {{name | stringify }}!";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: "John", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of('Hello, "John"!'));
});

it("Spaces around transformations AND variables should be ignored", () => {
const template = "Hello, {{ name | stringify }}!";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: "John", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of('Hello, "John"!'));
});

it("Should execute a template with no transformations", () => {
const template = "Hello, {{name}}! You are {{age}} years old.";
const parsed = parseTemplate(template);
const result = pipe(
parsed,
E.map((parsedTemplate) => executeTemplate(parsedTemplate, { name: "John", age: 18 })),
E.map(tap("executed")),
);
expect(result).toEqual(E.of("Hello, John! You are 18 years old."));
});

it("should parse a frontmatter command", () => {
const template = "{#frontmatter#}";
const result = parseTemplate(template);
Expand Down
37 changes: 33 additions & 4 deletions src/core/template/templateParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { stringifyYaml } from "obsidian";
import * as P from "parser-ts/Parser";
import * as C from "parser-ts/char";
import * as S from "parser-ts/string";
import { ModalFormData } from "../FormResult";
import { ModalFormData, Val } from "../FormResult";
import {
transformations,
type FrontmatterCommand,
Expand Down Expand Up @@ -44,7 +44,7 @@ const close = P.expected(S.fold([S.spaces, S.string("}}")]), 'closing variable t
const identifier = S.many1(C.alphanum);
const transformation = pipe(
// dam prettier
S.fold([S.string("|"), S.spaces]),
S.fold([S.spaces, S.string("|"), S.spaces]),
P.apSecond(identifier),
P.map((x) => {
return pipe(
Expand Down Expand Up @@ -141,6 +141,9 @@ export function parseTemplate(template: string): Either<string, ParsedTemplate>
// return S.run(template)(P.many(Template))
}

/**
* Given a parsed template, returns a list of the variables used in the template
*/
export function templateVariables(parsedTemplate: ReturnType<typeof parseTemplate>): string[] {
return pipe(
parsedTemplate,
Expand Down Expand Up @@ -186,7 +189,7 @@ function tokenToString(token: Token): string {

function matchToken<T>(
onText: (value: string) => T,
onVariable: (variable: string, transformation?: string) => T,
onVariable: (variable: string, transformation?: Transformations) => T,
onCommand: (command: FrontmatterCommand) => T,
) {
return (token: Token): T => {
Expand Down Expand Up @@ -232,14 +235,40 @@ function asFrontmatterString(data: Record<string, unknown>) {
);
}

function executeTransformation(
transformation: Transformations | undefined,
): (value: Val) => string {
return (value) => {
if (transformation === undefined) {
return String(value);
}
switch (transformation) {
case "upper":
return String(value).toUpperCase();
case "lower":
return String(value).toLowerCase();
case "stringify":
return JSON.stringify(value);
case "trim":
return String(value).trim();
default:
return absurd(transformation);
}
};
}

export function executeTemplate(parsedTemplate: ParsedTemplate, formData: ModalFormData) {
const toFrontmatter = asFrontmatterString(formData); // Build it upfront rater than on every call
return pipe(
parsedTemplate,
A.filterMap(
matchToken(
O.some,
(key, transformation) => O.fromNullable(formData[key]),
(key, transformation) =>
pipe(
O.fromNullable(formData[key]),
O.map(executeTransformation(transformation)),
),
(command) =>
pipe(
//prettier
Expand Down

0 comments on commit 7552a2a

Please sign in to comment.