Skip to content

Commit

Permalink
feat: allow pick/omit in Result helpers
Browse files Browse the repository at this point in the history
this also includes the objectSelect utility and it's tests
fixes #101
  • Loading branch information
danielo515 committed Oct 26, 2023
1 parent efc05a4 commit 1c5bd5f
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 21 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"@unsplash/ts-namespace-import-plugin": "^1.0.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"esbuild-svelte": "^0.8.0",
Expand Down
36 changes: 20 additions & 16 deletions src/core/FormResult.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { objectSelect } from './objectSelect';
import { stringifyYaml } from "obsidian";
import { log_error } from "../utils/Log";
import { ModalFormError } from "../utils/Error";
import { object, optional, array, string, coerce } from "valibot";
import { parse } from "@std";

type ResultStatus = "ok" | "cancelled";

Expand All @@ -17,16 +16,6 @@ function isPrimitiveArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(isPrimitive)
}

const KeysSchema = array(coerce(string(), String))

const PickOmitSchema = object({
pick: optional(KeysSchema),
omit: optional(KeysSchema),
});

console.log(parse(PickOmitSchema, { pick: ['a', 'b'] }))
console.log(parse(PickOmitSchema, { pick: [11], omit: ['a', 'b'] }))
console.log(parse(PickOmitSchema, undefined))

export function formDataFromFormOptions(values: Record<string, unknown>) {
const result: ModalFormData = {};
Expand All @@ -48,15 +37,30 @@ export function formDataFromFormOptions(values: Record<string, unknown>) {

export default class FormResult {
constructor(private data: ModalFormData, public status: ResultStatus) { }
asFrontmatterString() {
return stringifyYaml(this.data);
/**
* Transform the current data into a frontmatter string, which is expected
* to be enclosed in `---` when used in a markdown file.
* This method does not add the enclosing `---` to the string,
* so you can put it anywhere inside the frontmatter.
* @param {Object} [options] an options object describing what options to pick or omit
* @param {string[]} [options.pick] an array of key names to pick from the data
* @param {string[]} [options.omit] an array of key names to omit from the data
* @returns the data formatted as a frontmatter string
*/
asFrontmatterString(options?: unknown) {
const data = objectSelect(this.data, options)
return stringifyYaml(data);
}
/**
* Return the current data as a block of dataview properties
* @param {Object} [options] an options object describing what options to pick or omit
* @param {string[]} [options.pick] an array of key names to pick from the data
* @param {string[]} [options.omit] an array of key names to omit from the data
* @returns string
*/
asDataviewProperties(): string {
return Object.entries(this.data)
asDataviewProperties(options?: unknown): string {
const data = objectSelect(this.data, options)
return Object.entries(data)
.map(([key, value]) =>
`${key}:: ${Array.isArray(value) ? value.map((v) => JSON.stringify(v)) : value}`
)
Expand Down
59 changes: 59 additions & 0 deletions src/core/objectSelect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { objectSelect } from "./objectSelect";

describe("objectSelect", () => {
const obj = {
name: "John Doe",
age: 30,
hobbies: ["reading", "swimming"],
isEmployed: true,
};

it("should return the original object if no options are provided", () => {
expect(objectSelect(obj, {})).toEqual(obj);
});

it("should pick the specified properties from the object", () => {
const opts = { pick: ["name", "age"] };
const expectedOutput = { name: "John Doe", age: 30 };
expect(objectSelect(obj, opts)).toEqual(expectedOutput);
});

it("should omit the specified properties from the object", () => {
const opts = { omit: ["name", "hobbies"] };
const expectedOutput = { age: 30, isEmployed: true };
expect(objectSelect(obj, opts)).toEqual(expectedOutput);
});

it("should pick and omit properties from the object", () => {
const opts = { pick: ["name", "age"], omit: ["age"] };
const expectedOutput = { name: "John Doe" };
expect(objectSelect(obj, opts)).toEqual(expectedOutput);
});

it("should return the original object if the pick array is empty", () => {
const opts = { pick: [] };
expect(objectSelect(obj, opts)).toEqual(obj);
});

it("should return an empty object if the omit array contains all properties", () => {
const opts = { omit: ["name", "age", "hobbies", "isEmployed"] };
expect(objectSelect(obj, opts)).toEqual({});
});

it("should return the original object if the omit array is empty", () => {
const opts = { omit: [] };
expect(objectSelect(obj, opts)).toEqual(obj);
});

it("should return the original object if the options argument is not an object", () => {
expect(objectSelect(obj, "invalid options")).toEqual(obj);
});

it("should return the original object if the options argument is null", () => {
expect(objectSelect(obj, null)).toEqual(obj);
});

it("should return the original object if the options argument is undefined", () => {
expect(objectSelect(obj, undefined)).toEqual(obj);
});
});
46 changes: 46 additions & 0 deletions src/core/objectSelect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { E, parse, pipe } from "@std";
import * as O from "fp-ts/Option";
import * as NEA from "fp-ts/NonEmptyArray";
import { filterWithIndex } from "fp-ts/lib/Record";
import { object, optional, array, string, coerce } from "valibot";

const KeysSchema = array(coerce(string(), String))

const PickOmitSchema = object({
pick: optional(KeysSchema),
omit: optional(KeysSchema),
});



/**
* Utility to pick/omit keys from an object.
* It is user facing, that's why we are so defensive in the options.
* The object should come from the form results, so we don't need to be that strict.
* @param obj the object you want to pick/omit from
* @param opts the options for picking/omitting based on key names
*/
export function objectSelect(obj: Record<string, unknown>, opts: unknown): Record<string, unknown> {
return pipe(
parse(PickOmitSchema, opts, { abortEarly: true }),
E.map((opts) => {
const picked = pipe(
O.fromNullable(opts.pick),
O.flatMap(NEA.fromArray),
O.map((pick) => {
return filterWithIndex((k) => pick.includes(k))(obj);
}),
O.getOrElse(() => obj)
);
return pipe(
O.fromNullable(opts.omit),
O.map((omit) =>
filterWithIndex((k) => !omit.includes(k))(picked)),
O.getOrElse(() => picked)
)
}
),
E.getOrElse(() => obj)
)
}

6 changes: 3 additions & 3 deletions src/std/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { pipe as p } from "fp-ts/function";
import { partitionMap } from "fp-ts/Array";
import { isLeft, isRight, tryCatchK } from "fp-ts/Either";
import { isLeft, isRight, tryCatchK, map, getOrElse } from "fp-ts/Either";
import { ValiError, parse as parseV } from "valibot";

export const pipe = p
Expand All @@ -12,8 +12,8 @@ export const E = {
isLeft,
isRight,
tryCatchK,
getOrElse,
map
}

export const O = {}

export const parse = tryCatchK(parseV, (e: unknown) => e as ValiError)
17 changes: 15 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,22 @@
"@std": [
"src/std"
]
}
},
"plugins": [
{
"name": "@unsplash/ts-namespace-import-plugin",
"namespaces": {
"O": {
"importPath": "fp-ts/Option"
},
"NEA": {
"importPath": "fp-ts/NonEmptyArray"
}
}
}
]
},
"include": [
"src/**/*"
]
],
}

0 comments on commit 1c5bd5f

Please sign in to comment.