diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/flatmatter.test.ts | 173 | ||||
| -rw-r--r-- | src/flatmatter.ts | 552 | ||||
| -rw-r--r-- | src/index.ts | 11 | ||||
| -rw-r--r-- | src/serializers/to_json.test.ts | 6 | ||||
| -rw-r--r-- | src/serializers/to_json.ts | 12 | ||||
| -rw-r--r-- | src/serializers/to_object.test.ts | 171 | ||||
| -rw-r--r-- | src/serializers/to_object.ts | 7 |
7 files changed, 448 insertions, 484 deletions
diff --git a/src/flatmatter.test.ts b/src/flatmatter.test.ts index 9f00ef9..8e480fc 100644 --- a/src/flatmatter.test.ts +++ b/src/flatmatter.test.ts @@ -1,32 +1,185 @@ import FlatMatter from "./flatmatter.ts"; import { assert } from "vitest"; -import ToObject from "./serializers/to_object.ts"; + +test("Single-level configuration", () => { + const config = FlatMatter.config( + 'a: true\nb: false\nc: 1\nd: 12.5\nf: "some string"', + ); + + expect(config).toStrictEqual({ + a: true, + b: false, + c: 1, + d: 12.5, + f: "some string", + }); +}); + +test("Two-level configuration", () => { + const config = FlatMatter.config( + 'a.a: true\nb.b: false\nc.c: 1\nd.d: 12.5\nf.f: "some string"', + ); + + expect(config).toStrictEqual({ + a: { + a: true, + }, + b: { + b: false, + }, + c: { + c: 1, + }, + d: { + d: 12.5, + }, + f: { + f: "some string", + }, + }); +}); + +test("Simple function usage", () => { + const toUpper = { + name: "to-upper", + compute: (input: string): unknown => { + return input.toUpperCase(); + }, + }; + + const config = FlatMatter.config('a: (to-upper "value")', [toUpper]); + + expect(config).toStrictEqual({ + a: "VALUE", + }); +}); + +test("Piped function by reference usage", () => { + const toUpper = { + name: "to-upper", + compute: (input: string): unknown => { + return input.toUpperCase(); + }, + }; + + const config = FlatMatter.config('a: "value" / to-upper', [toUpper]); + + expect(config).toStrictEqual({ + a: "VALUE", + }); +}); + +test("Piped function by call usage", () => { + const toUpper = { + name: "to-upper", + compute: (input: string, additional: number): unknown => { + return `${input.toUpperCase()}-${additional}`; + }, + }; + + const config = FlatMatter.config('a: "value" / (to-upper 123)', [toUpper]); + + expect(config).toStrictEqual({ + a: "VALUE-123", + }); +}); + +test("Invalid value in pipe", () => { + const config = FlatMatter.config('a: "value" / / asd'); + + expect(config).toStrictEqual({}); +}); + +test("Invalid value in pipe, 2", () => { + const config = FlatMatter.config('a: "value" / asd'); + + expect(config).toStrictEqual({ + a: "value", + }); +}); + +test("Only piped functions", () => { + const firstFn = { + name: "first-fn", + compute: (input: string): unknown => { + return input.toUpperCase(); + }, + }; + + const secondFn = { + name: "second-fn", + compute: (input: string): unknown => { + return `${input}-passed-by-second`; + }, + }; + + const config = FlatMatter.config('a: (first-fn "value / here") / second-fn', [ + firstFn, + secondFn, + ]); + + expect(config).toStrictEqual({ + a: "VALUE / HERE-passed-by-second", + }); +}); + +test("Function call without any args", () => { + const toUpper = { + name: "to-upper", + compute: (input: string): unknown => { + return input.toUpperCase(); + }, + }; + + const config = FlatMatter.config('a: "value" / (to-upper)', [toUpper]); + + expect(config).toStrictEqual({ + a: "VALUE", + }); +}); + +test("Function call using multiple strings with spaces as arg", () => { + const toUpper = { + name: "to-upper", + compute: (input: string): unknown => { + return input.toUpperCase(); + }, + }; + + const config = FlatMatter.config( + 'a: (to-upper "value goes here" "and here")', + [toUpper], + ); + + expect(config).toStrictEqual({ + a: "VALUE GOES HERE", + }); +}); test("Line has no value separator", () => { assert.throws( - () => new FlatMatter("test"), - "Line on index 0 doesn't have a value separator." + () => FlatMatter.config("test"), + "Line on index 0 doesn't have a value separator.", ); }); test("Line can only have one value separator", () => { assert.throws( - () => new FlatMatter("test: this: that"), - "Line on index 0 has multiple value separators." + () => FlatMatter.config("test: this: that"), + "Line on index 0 has multiple value separators.", ); }); test("String values can have colon characters", () => { - assert.doesNotThrow(() => new FlatMatter('test: "this : that"'), Error); + assert.doesNotThrow(() => FlatMatter.config('test: "this : that"'), Error); }); test("FrontMatter creates a new content entry", () => { - const fm = new FlatMatter( - `---\nthis: true\n---\n\nMarkdown goes here.\n\nAnd here.` + const config = FlatMatter.config( + `---\nthis: true\n---\n\nMarkdown goes here.\n\nAnd here.`, ); - const result = fm.serialize(new ToObject()); - assert.deepEqual(result, { + assert.deepEqual(config, { this: true, content: "Markdown goes here.\n\nAnd here.", }); diff --git a/src/flatmatter.ts b/src/flatmatter.ts index e81df1f..1185020 100644 --- a/src/flatmatter.ts +++ b/src/flatmatter.ts @@ -1,106 +1,63 @@ import { EOL } from "node:os"; +import * as Effect from "effect/Effect"; +import * as Context from "effect/Context"; +import * as Ref from "effect/Ref"; +import * as Cause from "effect/Cause"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { trimChar } from "./utils.ts"; +import ToJson from "./serializers/to_json.ts"; -export type ParsedValue = { - value: unknown; - computeActions: ComputeAction[]; -}; - -export type ComputeAction = { - identifier: string; - args: Array<unknown>; -}; +const ComputeAction = Schema.Struct({ + identifier: Schema.NonEmptyString, + args: Schema.Array(Schema.Unknown), +}); -export interface Serializer<T> { - serialize(config: Record<string, unknown>): T; -} +const ParsedValue = Schema.Struct({ + value: Schema.Unknown, + computeActions: Schema.Array(ComputeAction), +}); -export interface FlatMatterFn { +type Function = { name: string; - compute(...args: unknown[]): unknown; -} - -export default class FlatMatter { - private content: string; - private parsedConfig: Record<string, unknown> = {}; - private functions: FlatMatterFn[]; - - constructor(content: string, functions: FlatMatterFn[] = []) { - this.content = content; - this.functions = functions; - this.parse(); - } - - private parse(): void { - const lines = this.content.split(EOL); - let frontMatterBreakCount = 0; - - for (let i = 0; i < lines.length; i++) { - if (lines[i].trim() === "---" && frontMatterBreakCount < 2) { - frontMatterBreakCount++; - continue; - } - - // FlatMatter ends, Markdown begins - if (frontMatterBreakCount < 2) { - this.parseLine(i, lines[i]); - continue; - } - - this.parsedConfig.content = lines.slice(i).join(EOL).trim(); - break; - } - } - - /** - * Parses a given line of FlatMatter. - */ - private parseLine(idx: number, line: string): void { - this.validateLineConformance(idx, line); - - const keys = line.split(":")[0].trim().split("."); - const value = line.split(":").slice(1).join(":").trim(); - const parsedValue = this.parseValue(value); - - if (!parsedValue) return; - - const config = keys.reduceRight((acc, key) => { - return { [key]: acc }; - }, this.computeValue(parsedValue)) as Record<string, unknown>; - - this.parsedConfig = { ...this.parsedConfig, ...config }; - } - - /** - * For better developer experience, this validates each line - * against some common mistakes you can make, and throws an Error - * if you did. - */ - private validateLineConformance(idx: number, line: string): void { - const validators = [ - this.validateLineHasKeyVal, - this.validateLineHasOnlyOneColonChar, - ]; - - for (const validator of validators) { - validator(idx, line); - } - } +}; - /** - * Validates that the given line has a value separator. - */ - private validateLineHasKeyVal(idx: number, line: string): void { +class FunctionsState extends Context.Tag("FunctionsState")< + FunctionsState, + Ref.Ref<Function[]> +>() {} + +/** + * State for holding the content string. + */ +class ContentState extends Context.Tag("ContentState")< + ContentState, + Ref.Ref<string> +>() {} + +/** + * State for holding the parsed configuration. + */ +class ConfigState extends Context.Tag("ConfigState")< + ConfigState, + Ref.Ref<Record<string, unknown>> +>() {} + +const validateLineHasKeyValEffect = (idx: number, line: string) => + Effect.gen(function* () { if (!line.includes(":")) { - throw new Error(`Line on index ${idx} doesn't have a value separator.`); + yield* Effect.fail( + Cause.fail(`Line on index ${idx} doesn't have a value separator.`), + ); } - } + }); - /** - * Validates that the given line has only one value separator. - */ - private validateLineHasOnlyOneColonChar(idx: number, line: string): void { +/** + * Validates that the given line has only one value separator. + */ +const validateLineHasOnlyOneColonCharEffect = (idx: number, line: string) => + Effect.gen(function* () { let separatorCount = 0; let parts = line.split(":").slice(1); @@ -114,242 +71,283 @@ export default class FlatMatter { } if (separatorCount > 1) { - throw new Error(`Line on index ${idx} has multiple value separators.`); + yield* Effect.fail( + Cause.fail(`Line on index ${idx} has multiple value separators.`), + ); } - } + }); + +const validateLineConformanceEffect = (idx: number, line: string) => + Effect.gen(function* () { + const validatorEffects = [ + validateLineHasKeyValEffect, + validateLineHasOnlyOneColonCharEffect, + ]; + + for (const validatorEffect of validatorEffects) { + yield* validatorEffect(idx, line); + } + }); - /** - * Detects if the value is a simple value. A simple value is any - * of the following: `"a string"`, boolean `true` or `false`, or - * anything numeric like `12345` or `123.45`. - */ - private isSimpleValue(value: string): boolean { - const isString = value.startsWith('"') && value.endsWith('"'); - const isBoolean = value === "true" || value === "false"; - const isNumber = !isNaN(parseFloat(value)); - - return isString || isBoolean || isNumber; +const isSimpleValue = (value: string): boolean => { + const isString = value.startsWith('"') && value.endsWith('"'); + const isBoolean = value === "true" || value === "false"; + const isNumber = !isNaN(parseFloat(value)); + + return isString || isBoolean || isNumber; +}; + +const parseSimpleValue = (value: string): string | number | boolean => { + if (value === "true" || value === "false") { + return value === "true"; } - /** - * Detects if the value is a function value. A function value is any - * of the following: - * - * - A function call with arguments: `(function-name *args)` - * - A function call by reference: `function-name` - */ - private isFunctionValue(value: string): boolean { - const isFnCall = value.startsWith("(") && value.endsWith(")"); - const isFnReference = !!value.match(/^([a-zA-Z0-9_-]+)$/); - - return isFnCall || isFnReference; + if (!Number.isNaN(parseInt(value)) && value.indexOf(".") === -1) { + return parseInt(value); } - /** - * Detects if the value is a piped value. A piped value is a mix of - * simple and function value parts, piped together with the forward - * slash `/` character. For example: - * - * ```yaml - * posts: (get-content "posts") / (limit 10) / only-published - * ``` - * - * or: - * - * ```yaml - * posts: "posts" / get-content / (limit 10) / only-published - * ``` - * - * The result of the previous pipe gets passed to the next as a first - * argument. - */ - private isPipedValue(value: string): boolean { - return this.composePipedValueParts(value).every((part) => { - return this.isSimpleValue(part) || this.isFunctionValue(part); - }); + if (!Number.isNaN(parseFloat(value)) && value.indexOf(".") !== -1) { + return parseFloat(value); } - /** - * Parses a value to a `ParsedValue` object, or `null` - * in case it could not for whatever reason. - */ - private parseValue(value: string): ParsedValue | null { - if (this.isSimpleValue(value)) { - return { - value: this.parseSimpleValue(value), - computeActions: [], - }; - } + return trimChar(value, '"'); +}; - if (this.isFunctionValue(value)) { - return { - value: null, - computeActions: [this.parseFunctionValue(value)], - }; - } +const isFunctionValue = (value: string): boolean => { + const isFnCall = value.startsWith("(") && value.endsWith(")"); + const isFnReference = !!value.match(/^([a-zA-Z0-9_-]+)$/); - if (this.isPipedValue(value)) { - return this.parsePipedValue(value); - } + return isFnCall || isFnReference; +}; - return null; +const parseFunctionValueArgs = (value: string): unknown[] => { + const parts = value + .substring(1, value.length - 1) + .split(" ") + .slice(1); + + if (!parts.length) { + return []; } - /** - * Parses the value part of a line into a simple value, like for example - * a `string`, `number` or `boolean`. - */ - private parseSimpleValue(value: string): string | number | boolean { - if (value === "true" || value === "false") { - return value === "true"; - } + const normalizedParts = [parts[0]]; - if (!Number.isNaN(parseInt(value)) && value.indexOf(".") === -1) { - return parseInt(value); - } + for (let i = 1; i < parts.length; i++) { + const untilCurrent = normalizedParts.join(" "); + const quoteCount = untilCurrent.split('"').length - 1; - if (!Number.isNaN(parseFloat(value)) && value.indexOf(".") !== -1) { - return parseFloat(value); + if (quoteCount % 2 === 0) { + normalizedParts.push(parts[i]); + continue; } - return trimChar(value, '"'); + const lastIndex = normalizedParts.length - 1; + const lastPart = normalizedParts[lastIndex]; + + normalizedParts[lastIndex] = `${lastPart} ${parts[i]}`; } - /** - * Parses the value part of a line into a Compute Action, which is - * later executed to run the function described in FlatMatter. - */ - private parseFunctionValue(value: string): ComputeAction { - const isFn = value.startsWith("(") && value.endsWith(")"); - - if (!isFn) { - return { - identifier: value, - args: [], - }; - } + return normalizedParts.map((part) => parseSimpleValue(part)); +}; - const fnName = trimChar(value, ["(", ")"]).split(" ")[0].trim(); - const fnArgs = this.parseFunctionValueArgs(value); +const parseFunctionValue = ( + value: string, +): Schema.Schema.Type<typeof ComputeAction> => { + const isFn = value.startsWith("(") && value.endsWith(")"); - return { - identifier: fnName, - args: fnArgs, - }; + if (!isFn) { + return ComputeAction.make({ + identifier: value, + args: [], + }); } - /** - * Parses the value part of a line into a ParsedValue, which is - * composed out of piped parts separated by the forward slash `/` character. - * - * The ParsedValue will include the default value, if any, and a list of compute - * actions which will later be executed. - */ - private parsePipedValue(value: string): ParsedValue { - const parts = this.composePipedValueParts(value); - - if (this.isSimpleValue(parts[0])) { - return { - value: this.parseSimpleValue(parts[0]), - computeActions: parts.slice(1).map((p) => this.parseFunctionValue(p)), - }; - } + const fnName = trimChar(value, ["(", ")"]).split(" ")[0].trim(); + const fnArgs = parseFunctionValueArgs(value); - return { - value: null, - computeActions: parts.map((p) => this.parseFunctionValue(p)), - }; - } + return ComputeAction.make({ + identifier: fnName, + args: fnArgs, + }); +}; + +const composePipedValueParts = (value: string): string[] => { + const parts = value.split(" / "); + const normalizedParts = [parts[0]]; + + for (let i = 1; i < parts.length; i++) { + const untilCurrent = normalizedParts.join(" / "); + const quoteCount = untilCurrent.split('"').length - 1; - /** - * Takes the entire value part of a line and, assuming it is a function value, - * parses it into a list of arguments to be passed down to the function. - */ - private parseFunctionValueArgs(value: string): unknown[] { - const parts = value - .substring(1, value.length - 1) - .split(" ") - .slice(1); - - if (!parts.length) { - return []; + if (quoteCount % 2 === 0) { + normalizedParts.push(parts[i]); + continue; } - const normalizedParts = [parts[0]]; + const lastIndex = normalizedParts.length - 1; + const lastPart = normalizedParts[lastIndex]; - for (let i = 1; i < parts.length; i++) { - const untilCurrent = normalizedParts.join(" "); - const quoteCount = untilCurrent.split('"').length - 1; + normalizedParts[lastIndex] = `${lastPart} / ${parts[i]}`; + } - if (quoteCount % 2 === 0) { - normalizedParts.push(parts[i]); - continue; - } + return normalizedParts; +}; - const lastIndex = normalizedParts.length - 1; - const lastPart = normalizedParts[lastIndex]; +const isPipedValue = (value: string): boolean => { + return composePipedValueParts(value).every((part) => { + return isSimpleValue(part) || isFunctionValue(part); + }); +}; - normalizedParts[lastIndex] = `${lastPart} ${parts[i]}`; - } +const parsePipedValue = (value: string): typeof ParsedValue.Type => { + const parts = composePipedValueParts(value); - return normalizedParts.map((part) => this.parseSimpleValue(part)); + if (isSimpleValue(parts[0])) { + return ParsedValue.make({ + value: parseSimpleValue(parts[0]), + computeActions: parts.slice(1).map((p) => parseFunctionValue(p)), + }); } - /** - * Takes an entire value of a line and composes it into a list - * of piped parts. - */ - private composePipedValueParts(value: string): string[] { - const parts = value.split(" / "); - const normalizedParts = [parts[0]]; - - for (let i = 1; i < parts.length; i++) { - const untilCurrent = normalizedParts.join(" / "); - const quoteCount = untilCurrent.split('"').length - 1; + return ParsedValue.make({ + value: null, + computeActions: parts.map((p) => parseFunctionValue(p)), + }); +}; - if (quoteCount % 2 === 0) { - normalizedParts.push(parts[i]); - continue; - } +const parseValueEffect = (value: string) => + Effect.gen(function* () { + if (isSimpleValue(value)) { + return Option.some( + ParsedValue.make({ + value: parseSimpleValue(value), + computeActions: [], + }), + ); + } - const lastIndex = normalizedParts.length - 1; - const lastPart = normalizedParts[lastIndex]; + if (isFunctionValue(value)) { + return Option.some( + ParsedValue.make({ + value: null, + computeActions: [parseFunctionValue(value)], + }), + ); + } - normalizedParts[lastIndex] = `${lastPart} / ${parts[i]}`; + if (isPipedValue(value)) { + return Option.some(parsePipedValue(value)); } - return normalizedParts; - } + return Option.none(); + }); - /** - * Takes ParsedValue and, optionally an initial value, and runs - * compute actions over it to return the final computed value. - */ - private computeValue(parsedValue: ParsedValue): unknown { +const computeValueEffect = (parsedValue: typeof ParsedValue.Type) => + Effect.gen(function* () { let value = parsedValue.value; + const functions = yield* Ref.get(yield* FunctionsState); - for (const ca of parsedValue.computeActions) { - const fnInstance = this.functions.find((f) => f.name === ca.identifier); + for (const computeAction of parsedValue.computeActions) { + const fn = functions.find((f) => f.name === computeAction.identifier); - if (!fnInstance) { + if (!fn) { continue; } if (value !== null) { - ca.args = [value, ...ca.args]; + value = fn.compute(value, ...computeAction.args); + continue; } - value = fnInstance.compute(...ca.args); + value = fn.compute(...computeAction.args); } return value; - } + }); + +const parseLineEffect = (idx: number, line: string) => + Effect.gen(function* () { + yield* validateLineConformanceEffect(idx, line); + + const keys = line.split(":")[0].trim().split("."); + const value = line.split(":").slice(1).join(":").trim(); + const parsedValue = yield* parseValueEffect(value); + + if (Option.isNone(parsedValue)) return; + + const updatedConfig = keys.reduceRight( + (acc, key) => { + return { [key]: acc }; + }, + yield* computeValueEffect(parsedValue.value), + ) as Record<string, unknown>; + + yield* Ref.update(yield* ConfigState, (config) => { + return { ...config, ...updatedConfig }; + }); + }); + +const parseContentEffect = Effect.gen(function* () { + const content = yield* Ref.get(yield* ContentState); + const lines = content.split(EOL); + let frontMatterBreakCount = 0; - /** - * Takes a Serializer and uses it to transform internal data - * object to a desired output. - */ - public serialize<T>(serializer: Serializer<T>): T { - return serializer.serialize(this.parsedConfig); + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim() === "---" && frontMatterBreakCount < 2) { + frontMatterBreakCount++; + continue; + } + + if (frontMatterBreakCount < 2) { + yield* parseLineEffect(i, lines[i]); + continue; + } + + // FlatMatter ends, Markdown begins + yield* Ref.update(yield* ConfigState, (config) => { + config.content = lines.slice(i).join(EOL).trim(); + return config; + }); + + break; } -} +}); + +/** + * + */ +const composeConfigEffect = Effect.gen(function* () { + yield* parseContentEffect; + + return yield* Ref.get(yield* ConfigState); +}); + +const config = ( + content: string, + functions: Function[] = [], +): Record<string, unknown> => { + return Effect.runSync( + composeConfigEffect.pipe( + Effect.provideServiceEffect(ContentState, Ref.make(content)), + Effect.provideServiceEffect(ConfigState, Ref.make({})), + Effect.provideServiceEffect(FunctionsState, Ref.make(functions)), + ), + ); +}; + +export type Serializer<T> = (config: Record<string, unknown>) => T; + +const serialize = <T,>( + config: Record<string, unknown>, + serializer: Serializer<T>, +): T => { + return serializer(config); +}; + +export default { + config, + serialize, + Serializers: { + ToJson, + }, +}; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 7ac5415..0000000 --- a/src/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import FlatMatter, {FlatMatterFn, Serializer} from "./flatmatter.ts"; -import ToObject from "./serializers/to_object.ts"; -import ToJson from "./serializers/to_json.ts"; - -export { - FlatMatter, - FlatMatterFn, - Serializer, - ToObject, - ToJson, -}
\ No newline at end of file diff --git a/src/serializers/to_json.test.ts b/src/serializers/to_json.test.ts index 4184ab1..78f704a 100644 --- a/src/serializers/to_json.test.ts +++ b/src/serializers/to_json.test.ts @@ -2,11 +2,11 @@ import FlatMatter from "../flatmatter.ts"; import ToJson from "./to_json.ts"; test("Single-level configuration", () => { - const fm = new FlatMatter( - 'a: true\nb: false\nc: 1\nd: 12.5\nf: "some string"' + const config = FlatMatter.config( + 'a: true\nb: false\nc: 1\nd: 12.5\nf: "some string"', ); const equal = '{"a":true,"b":false,"c":1,"d":12.5,"f":"some string"}'; - expect(fm.serialize(new ToJson())).toStrictEqual(equal); + expect(FlatMatter.serialize(config, ToJson)).toStrictEqual(equal); }); diff --git a/src/serializers/to_json.ts b/src/serializers/to_json.ts index 30cf417..c61a98f 100644 --- a/src/serializers/to_json.ts +++ b/src/serializers/to_json.ts @@ -1,7 +1,9 @@ import type { Serializer } from "../flatmatter.ts"; -export default class ToJson implements Serializer<string> { - serialize(config: Record<string, unknown>): string { - return JSON.stringify(config); - } -} +const ToJson: Serializer<string> = ( + config: Record<string, unknown>, +): string => { + return JSON.stringify(config); +}; + +export default ToJson; diff --git a/src/serializers/to_object.test.ts b/src/serializers/to_object.test.ts deleted file mode 100644 index 28cb78e..0000000 --- a/src/serializers/to_object.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import FlatMatter, { type FlatMatterFn } from "../flatmatter.ts"; -import ToObject from "./to_object.ts"; - -test("Single-level configuration", () => { - const fm = new FlatMatter( - 'a: true\nb: false\nc: 1\nd: 12.5\nf: "some string"' - ); - - expect(fm.serialize(new ToObject())).toStrictEqual({ - a: true, - b: false, - c: 1, - d: 12.5, - f: "some string", - }); -}); - -test("Two-level configuration", () => { - const fm = new FlatMatter( - 'a.a: true\nb.b: false\nc.c: 1\nd.d: 12.5\nf.f: "some string"' - ); - - expect(fm.serialize(new ToObject())).toStrictEqual({ - a: { - a: true, - }, - b: { - b: false, - }, - c: { - c: 1, - }, - d: { - d: 12.5, - }, - f: { - f: "some string", - }, - }); -}); - -test("Simple function usage", () => { - class ToUpper implements FlatMatterFn { - name = "to-upper"; - - compute(input: string): unknown { - return input.toUpperCase(); - } - } - - const fm = new FlatMatter('a: (to-upper "value")', [new ToUpper()]); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "VALUE", - }); -}); - -test("Piped function by reference usage", () => { - class ToUpper implements FlatMatterFn { - name = "to-upper"; - - compute(input: string): unknown { - return input.toUpperCase(); - } - } - - const fm = new FlatMatter('a: "value" / to-upper', [new ToUpper()]); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "VALUE", - }); -}); - -test("Piped function by call usage", () => { - class ToUpper implements FlatMatterFn { - name = "to-upper"; - - compute(input: string, additional: number): unknown { - return `${input.toUpperCase()}-${additional}`; - } - } - - const fm = new FlatMatter('a: "value" / (to-upper 123)', [new ToUpper()]); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "VALUE-123", - }); -}); - -test("Invalid value in pipe", () => { - const fm = new FlatMatter('a: "value" / / asd'); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({}); -}); - -test("Invalid value in pipe, 2", () => { - const fm = new FlatMatter('a: "value" / asd'); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "value", - }); -}); - -test("Only piped functions", () => { - class FirstFn implements FlatMatterFn { - name = "first-fn"; - - compute(input: string): unknown { - return input.toUpperCase(); - } - } - - class SecondFn implements FlatMatterFn { - name = "second-fn"; - - compute(input: string): unknown { - return `${input}-passed-by-second`; - } - } - - const fm = new FlatMatter('a: (first-fn "value / here") / second-fn', [ - new FirstFn(), - new SecondFn(), - ]); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "VALUE / HERE-passed-by-second", - }); -}); - -test("Function call without any args", () => { - class ToUpper implements FlatMatterFn { - name = "to-upper"; - - compute(input: string): unknown { - return input.toUpperCase(); - } - } - - const fm = new FlatMatter('a: "value" / (to-upper)', [new ToUpper()]); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "VALUE", - }); -}); - -test("Function call using multiple strings with spaces as arg", () => { - class ToUpper implements FlatMatterFn { - name = "to-upper"; - - compute(input: string): unknown { - return input.toUpperCase(); - } - } - - const fm = new FlatMatter('a: (to-upper "value goes here" "and here")', [ - new ToUpper(), - ]); - const config = fm.serialize(new ToObject()); - - expect(config).toStrictEqual({ - a: "VALUE GOES HERE", - }); -}); diff --git a/src/serializers/to_object.ts b/src/serializers/to_object.ts deleted file mode 100644 index 79b2886..0000000 --- a/src/serializers/to_object.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Serializer } from "../flatmatter.ts"; - -export default class ToObject implements Serializer<Record<string, unknown>> { - serialize(config: Record<string, unknown>): Record<string, unknown> { - return config; - } -} |
