Skip to content

Commit

Permalink
feat(morphix): implement customFunctions
Browse files Browse the repository at this point in the history
  • Loading branch information
fraxken committed Jul 11, 2024
1 parent 9703eff commit ad57c3c
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 29 deletions.
71 changes: 46 additions & 25 deletions src/morphix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ import { morphix } from "@sigyn/morphix";
await morphix("Hello {name | capitalize}", { name: "john" });
```

> [!NOTE]
> `morphix()` is async because it supports async **functions**
> [!IMPORTANT]
> `morphix()` is asynchrone because it supports async **functions**
## 🌐 API

### `morphix`
Expand All @@ -68,22 +69,31 @@ Data to interpolate into template.

The keys should be a valid JS identifier or number (a-z, A-Z, 0-9).

**options**
Type: `object`

**ignoreMissing**
Type: `boolean`
Default: `false`
#### Options

By default, Morphix throws a MissingValueError when a placeholder resolves to undefined. With this option set to true, it simply ignores it and leaves the placeholder as is.

**transform**
Type: `(data: { value: unknown; key: string }) => unknown` (default: `({value}) => value)`)

Performs arbitrary operation for each interpolation. If the returned value was undefined, it behaves differently depending on the ignoreMissing option. Otherwise, the returned value will be interpolated into a string and embedded into the template.
```ts
type MorphixFunction = (value: string) => Promise<string> | string;
export interface MorphixOptions {
/**
* Performs arbitrary operations for each interpolation.
* If the returned value is undefined, the behavior depends on the ignoreMissing option.
* Otherwise, the returned value is converted to a string and embedded into the template.
*/
transform?: (data: { value: unknown; key: string }) => unknown;
/**
* By default, Morphix throws a MissingValueError when a placeholder resolves to undefined.
* If this option is set to true, it simply ignores the unresolved placeholder and leaves it as is.
*
* @default false
*/
ignoreMissing?: boolean;
customFunctions?: Record<string, MorphixFunction>;
}
```

**MissingValueError**
Exposed for instance checking.
> [!NOTE]
> MissingValueError is exported, which is useful for instance and type checking.

## 📦 Functions

Expand All @@ -97,20 +107,31 @@ Retrieve host of a given IP. It uses `dns.reverse`.

If it fails to retrieve the host, it returns the ip instead.

## 🖋️ Interfaces
### Adding custom functions

Morphix lets you inject your own custom functions.

```ts
interface MorphixOptions {
transform?: (data: {
value: unknown;
key: string;
}) => unknown;
ignoreMissing?: boolean;
}
import { morphix } from "@sigyn/morphix";
import assert from "node:assert";
const transformedData = await morphix(
"{ data | lower }",
{ data: "HELLO WORLD" },
{
customFunctions: {
lower: (value) => value.toLowerCase()
}
}
);
assert.strictEqual(
transformedData,
"hello world"
);
```

## Credits
This package is heavily inspired by [pupa](https://github.com/sindresorhus/pupa). Morphix is a fork with function support and doesn't support HTML escape.
This package is heavily inspired by [pupa](https://github.com/sindresorhus/pupa). Morphix is a fork that includes function support and does not support HTML escape.

## License
MIT
22 changes: 19 additions & 3 deletions src/morphix/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@
import { capitalize, dnsresolve } from "./functions";

// CONSTANTS
const kFunctions = {
const kDefaultFunctions: Record<string, MorphixFunction> = {
capitalize,
dnsresolve
};

export type MorphixFunction = (value: string) => Promise<string> | string;

export interface MorphixOptions {
/**
* Performs arbitrary operations for each interpolation.
* If the returned value is undefined, the behavior depends on the ignoreMissing option.
* Otherwise, the returned value is converted to a string and embedded into the template.
*/
transform?: (data: { value: unknown; key: string }) => unknown;
/**
* By default, Morphix throws a MissingValueError when a placeholder resolves to undefined.
* If this option is set to true, it simply ignores the unresolved placeholder and leaves it as is.
*
* @default false
*/
ignoreMissing?: boolean;
customFunctions?: Record<string, MorphixFunction>;
}

export class MissingValueError extends Error {
Expand All @@ -31,8 +45,10 @@ export async function morphix(
) {
const {
transform = ({ value }) => value,
ignoreMissing = false
ignoreMissing = false,
customFunctions = {}
} = options;
const functions = { ...customFunctions, ...kDefaultFunctions };

if (typeof template !== "string") {
throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``);
Expand Down Expand Up @@ -62,7 +78,7 @@ export async function morphix(
return String(transformedValue);
}

return await kFunctions[func](String(transformedValue));
return await functions[func](String(transformedValue));
};

const braceFnRegex = /{\s{0,1}([a-z0-9-.]*)\s{0,1}(?:\|\s{0,1}((?:(?!{)[a-z0-9-]*)*?)\s{0,1})?}/gi;
Expand Down
20 changes: 19 additions & 1 deletion src/morphix/test/index.spec.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { describe, it } from "node:test";
import esmock from "esmock";

// Import Internal Dependencies
import { morphix } from "../dist/index.mjs";
import { morphix, type MorphixFunction } from "../dist/index.mjs";

describe("Morphix", () => {
it("main", async() => {
Expand Down Expand Up @@ -100,4 +100,22 @@ describe("Morphix", () => {
});
assert.equal(await morphix("host: {foo | dnsresolve}", { foo: "8.8.8.8" }), "host: 8.8.8.8");
});

describe("customFunctions", () => {
it("should use a new custom 'replace' function", async() => {
const customFunctions: Record<string, MorphixFunction> = {
replace: (value) => value.replaceAll("o", "i")
};

const computedStr = await morphix(
"{ foo | replace }",
{ foo: "foo" },
{ customFunctions }
);
assert.strictEqual(
computedStr,
"fii"
);
});
});
});

0 comments on commit ad57c3c

Please sign in to comment.