summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/flatmatter.test.ts173
-rw-r--r--src/flatmatter.ts552
-rw-r--r--src/index.ts11
-rw-r--r--src/serializers/to_json.test.ts6
-rw-r--r--src/serializers/to_json.ts12
-rw-r--r--src/serializers/to_object.test.ts171
-rw-r--r--src/serializers/to_object.ts7
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;
- }
-}