From 7552a2a6095ff1fd51c1b0010abfc08edc53ffa3 Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Thu, 19 Dec 2024 21:35:36 +0100 Subject: [PATCH] feat(template): execute transformations when applying templates --- .../.obsidian/plugins/modal-form/data.json | 7 +- src/core/template/templateParser.test.ts | 78 +++++++++++++++++++ src/core/template/templateParser.ts | 37 ++++++++- 3 files changed, 115 insertions(+), 7 deletions(-) diff --git a/EXAMPLE_VAULT/.obsidian/plugins/modal-form/data.json b/EXAMPLE_VAULT/.obsidian/plugins/modal-form/data.json index 89ab684..28fa6ad 100644 --- a/EXAMPLE_VAULT/.obsidian/plugins/modal-form/data.json +++ b/EXAMPLE_VAULT/.obsidian/plugins/modal-form/data.json @@ -450,7 +450,6 @@ ], "version": "1", "template": { - "createCommand": true, "parsedTemplate": [ { "_tag": "text", @@ -514,7 +513,8 @@ }, { "_tag": "variable", - "value": "dateOfBirth" + "value": "dateOfBirth", + "transformation": "stringify" }, { "_tag": "text", @@ -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 } } ] diff --git a/src/core/template/templateParser.test.ts b/src/core/template/templateParser.test.ts index b33a1ba..c0895eb 100644 --- a/src/core/template/templateParser.test.ts +++ b/src/core/template/templateParser.test.ts @@ -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); diff --git a/src/core/template/templateParser.ts b/src/core/template/templateParser.ts index 99225f3..43a71f0 100644 --- a/src/core/template/templateParser.ts +++ b/src/core/template/templateParser.ts @@ -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, @@ -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( @@ -141,6 +141,9 @@ export function parseTemplate(template: string): Either // 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): string[] { return pipe( parsedTemplate, @@ -186,7 +189,7 @@ function tokenToString(token: Token): string { function matchToken( onText: (value: string) => T, - onVariable: (variable: string, transformation?: string) => T, + onVariable: (variable: string, transformation?: Transformations) => T, onCommand: (command: FrontmatterCommand) => T, ) { return (token: Token): T => { @@ -232,6 +235,28 @@ function asFrontmatterString(data: Record) { ); } +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( @@ -239,7 +264,11 @@ export function executeTemplate(parsedTemplate: ParsedTemplate, formData: ModalF 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