gray-matter-es 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/defaults.mjs +19 -0
- package/dist/defaults.mjs.map +1 -0
- package/dist/engines.d.mts +8 -0
- package/dist/engines.mjs +63 -0
- package/dist/engines.mjs.map +1 -0
- package/dist/excerpt.mjs +26 -0
- package/dist/excerpt.mjs.map +1 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +126 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_chars.mjs +45 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_chars.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_dumper_state.mjs +437 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_dumper_state.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_loader_state.mjs +909 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_loader_state.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_schema.mjs +115 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_schema.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/binary.mjs +89 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/binary.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/bool.mjs +35 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/bool.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/float.mjs +55 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/float.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/int.mjs +114 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/int.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/map.mjs +15 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/map.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/merge.mjs +11 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/merge.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/nil.mjs +20 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/nil.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/omap.mjs +28 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/omap.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/pairs.mjs +19 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/pairs.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/regexp.mjs +26 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/regexp.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/seq.mjs +11 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/seq.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/set.mjs +14 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/set.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/str.mjs +11 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/str.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/timestamp.mjs +54 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/timestamp.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/undefined.mjs +19 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_type/undefined.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_utils.mjs +14 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/_utils.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/mod.mjs +4 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/parse.mjs +50 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/parse.mjs.map +1 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/stringify.mjs +32 -0
- package/dist/node_modules/.pnpm/@jsr_std__yaml@1.0.10/node_modules/@jsr/std__yaml/stringify.mjs.map +1 -0
- package/dist/parse.mjs +13 -0
- package/dist/parse.mjs.map +1 -0
- package/dist/stringify.mjs +52 -0
- package/dist/stringify.mjs.map +1 -0
- package/dist/to-file.mjs +44 -0
- package/dist/to-file.mjs.map +1 -0
- package/dist/types.d.mts +85 -0
- package/dist/utils.mjs +60 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +61 -0
- package/src/defaults.ts +17 -0
- package/src/engines.ts +217 -0
- package/src/excerpt.ts +146 -0
- package/src/index.ts +481 -0
- package/src/parse.ts +9 -0
- package/src/stringify.ts +187 -0
- package/src/to-file.ts +178 -0
- package/src/types.ts +84 -0
- package/src/utils.ts +158 -0
package/src/to-file.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { GrayMatterFile, GrayMatterInput, GrayMatterOptions } from "./types.ts";
|
|
2
|
+
import { getStringProp, isObject, toBuffer, toString } from "./utils.ts";
|
|
3
|
+
import { stringify } from "./stringify.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal input shape after normalization
|
|
7
|
+
*/
|
|
8
|
+
interface NormalizedInput {
|
|
9
|
+
content: string | Buffer;
|
|
10
|
+
data?: unknown;
|
|
11
|
+
language?: string;
|
|
12
|
+
matter?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalize input to a consistent shape
|
|
17
|
+
*/
|
|
18
|
+
function normalizeInput(input: GrayMatterInput): NormalizedInput {
|
|
19
|
+
if (isObject(input)) {
|
|
20
|
+
return {
|
|
21
|
+
content: getStringProp(input, "content"),
|
|
22
|
+
data: input.data,
|
|
23
|
+
language: getStringProp(input, "language"),
|
|
24
|
+
matter: getStringProp(input, "matter"),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// string or Buffer
|
|
28
|
+
return { content: input };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Normalize the given value to ensure an object is returned
|
|
33
|
+
* with the expected properties.
|
|
34
|
+
*/
|
|
35
|
+
export function toFile(input: GrayMatterInput): GrayMatterFile {
|
|
36
|
+
const normalized = normalizeInput(input);
|
|
37
|
+
const data = isObject(normalized.data) ? normalized.data : {};
|
|
38
|
+
const content = toString(normalized.content ?? "");
|
|
39
|
+
const language = normalized.language ?? "";
|
|
40
|
+
const matter = normalized.matter ?? "";
|
|
41
|
+
const orig = toBuffer(normalized.content ?? "");
|
|
42
|
+
|
|
43
|
+
const file: GrayMatterFile = {
|
|
44
|
+
data,
|
|
45
|
+
content,
|
|
46
|
+
excerpt: "",
|
|
47
|
+
orig,
|
|
48
|
+
language,
|
|
49
|
+
matter,
|
|
50
|
+
isEmpty: false,
|
|
51
|
+
stringify(
|
|
52
|
+
this: GrayMatterFile,
|
|
53
|
+
newData?: Record<string, unknown>,
|
|
54
|
+
options?: GrayMatterOptions,
|
|
55
|
+
) {
|
|
56
|
+
if (options?.language) {
|
|
57
|
+
this.language = options.language;
|
|
58
|
+
}
|
|
59
|
+
return stringify(this, newData, options);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return file;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (import.meta.vitest) {
|
|
67
|
+
const { fc, test } = await import("@fast-check/vitest");
|
|
68
|
+
const { Buffer } = await import("node:buffer");
|
|
69
|
+
|
|
70
|
+
describe("toFile", () => {
|
|
71
|
+
it("should convert string to file object", () => {
|
|
72
|
+
const result = toFile("hello world");
|
|
73
|
+
expect(result.content).toBe("hello world");
|
|
74
|
+
expect(result.data).toEqual({});
|
|
75
|
+
expect(result.isEmpty).toBe(false);
|
|
76
|
+
expect(result.excerpt).toBe("");
|
|
77
|
+
expect(result.language).toBe("");
|
|
78
|
+
expect(result.matter).toBe("");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should convert Buffer to file object", () => {
|
|
82
|
+
const result = toFile(Buffer.from("buffer content"));
|
|
83
|
+
expect(result.content).toBe("buffer content");
|
|
84
|
+
expect(result.data).toEqual({});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should handle object with content property", () => {
|
|
88
|
+
const result = toFile({ content: "object content", data: { key: "value" } });
|
|
89
|
+
expect(result.content).toBe("object content");
|
|
90
|
+
expect(result.data).toEqual({ key: "value" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should preserve orig as Buffer", () => {
|
|
94
|
+
const result = toFile("test");
|
|
95
|
+
expect(Buffer.isBuffer(result.orig)).toBe(true);
|
|
96
|
+
expect(result.orig.toString()).toBe("test");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should set stringify as a function", () => {
|
|
100
|
+
const result = toFile("content");
|
|
101
|
+
expect(typeof result.stringify).toBe("function");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should initialize data to empty object if not provided", () => {
|
|
105
|
+
const result = toFile({ content: "test" });
|
|
106
|
+
expect(result.data).toEqual({});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should strip BOM from content", () => {
|
|
110
|
+
const bomString = "\uFEFFhello";
|
|
111
|
+
const result = toFile(bomString);
|
|
112
|
+
expect(result.content).toBe("hello");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should preserve existing language", () => {
|
|
116
|
+
const result = toFile({ content: "test", language: "json" } as GrayMatterInput);
|
|
117
|
+
expect(result.language).toBe("json");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should preserve existing matter", () => {
|
|
121
|
+
const result = toFile({ content: "test", matter: "raw matter" } as GrayMatterInput);
|
|
122
|
+
expect(result.matter).toBe("raw matter");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test.prop([fc.string({ minLength: 0, maxLength: 200 })])(
|
|
126
|
+
"should always return valid file object for any string",
|
|
127
|
+
(input) => {
|
|
128
|
+
const result = toFile(input);
|
|
129
|
+
expect(typeof result.content).toBe("string");
|
|
130
|
+
expect(typeof result.data).toBe("object");
|
|
131
|
+
expect(result.data).not.toBeNull();
|
|
132
|
+
expect(typeof result.isEmpty).toBe("boolean");
|
|
133
|
+
expect(typeof result.excerpt).toBe("string");
|
|
134
|
+
expect(typeof result.language).toBe("string");
|
|
135
|
+
expect(typeof result.matter).toBe("string");
|
|
136
|
+
expect(Buffer.isBuffer(result.orig)).toBe(true);
|
|
137
|
+
expect(typeof result.stringify).toBe("function");
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
test.prop([fc.uint8Array({ minLength: 0, maxLength: 200 })])(
|
|
142
|
+
"should handle any Buffer input",
|
|
143
|
+
(arr) => {
|
|
144
|
+
const buffer = Buffer.from(arr);
|
|
145
|
+
const result = toFile(buffer);
|
|
146
|
+
expect(typeof result.content).toBe("string");
|
|
147
|
+
expect(Buffer.isBuffer(result.orig)).toBe(true);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
test.prop([
|
|
152
|
+
fc.record({
|
|
153
|
+
content: fc.string({ minLength: 0, maxLength: 100 }),
|
|
154
|
+
data: fc.option(fc.dictionary(fc.string(), fc.string(), { maxKeys: 5 }), {
|
|
155
|
+
nil: undefined,
|
|
156
|
+
}),
|
|
157
|
+
}),
|
|
158
|
+
])("should handle object input with optional data", ({ content, data }) => {
|
|
159
|
+
const input = data !== undefined ? { content, data } : { content };
|
|
160
|
+
const result = toFile(input);
|
|
161
|
+
expect(result.content).toBe(content);
|
|
162
|
+
expect(typeof result.data).toBe("object");
|
|
163
|
+
if (data !== undefined) {
|
|
164
|
+
expect(result.data).toEqual(data);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test.prop([fc.string({ minLength: 1, maxLength: 50 })])(
|
|
169
|
+
"should strip BOM consistently",
|
|
170
|
+
(str) => {
|
|
171
|
+
const withBom = "\uFEFF" + str;
|
|
172
|
+
const result = toFile(withBom);
|
|
173
|
+
expect(result.content).toBe(str);
|
|
174
|
+
expect(result.content.charCodeAt(0)).not.toBe(0xfeff);
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine interface for parsing and stringifying front matter
|
|
3
|
+
*/
|
|
4
|
+
export interface Engine {
|
|
5
|
+
parse: (str: string) => Record<string, unknown>;
|
|
6
|
+
stringify?: (data: Record<string, unknown>) => string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for gray-matter
|
|
11
|
+
*/
|
|
12
|
+
export interface GrayMatterOptions {
|
|
13
|
+
/** Language to use for parsing (default: 'yaml') */
|
|
14
|
+
language?: "yaml" | "json";
|
|
15
|
+
/** Delimiters for front matter (default: '---') */
|
|
16
|
+
delimiters?: string | [string, string];
|
|
17
|
+
/**
|
|
18
|
+
* Extract an excerpt from the content.
|
|
19
|
+
* - `true`: use the default delimiter
|
|
20
|
+
* - `string`: use this string as the delimiter
|
|
21
|
+
* - `function`: custom excerpt function
|
|
22
|
+
*/
|
|
23
|
+
excerpt?: boolean | string | ((file: GrayMatterFile, options: GrayMatterOptions) => void);
|
|
24
|
+
/** Separator for excerpt in file.data */
|
|
25
|
+
excerpt_separator?: string;
|
|
26
|
+
/** Data to merge with parsed data */
|
|
27
|
+
data?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolved options with defaults applied
|
|
32
|
+
*/
|
|
33
|
+
export interface ResolvedOptions extends GrayMatterOptions {
|
|
34
|
+
delimiters: [string, string];
|
|
35
|
+
language: "yaml" | "json";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The file object returned by gray-matter
|
|
40
|
+
*/
|
|
41
|
+
export interface GrayMatterFile {
|
|
42
|
+
/** The parsed front matter data */
|
|
43
|
+
data: Record<string, unknown>;
|
|
44
|
+
/** The content after front matter */
|
|
45
|
+
content: string;
|
|
46
|
+
/** The extracted excerpt (if enabled) */
|
|
47
|
+
excerpt: string;
|
|
48
|
+
/** The original input as a Buffer */
|
|
49
|
+
orig: Buffer;
|
|
50
|
+
/** The detected/specified language */
|
|
51
|
+
language: string;
|
|
52
|
+
/** The raw front matter string (without delimiters) */
|
|
53
|
+
matter: string;
|
|
54
|
+
/** True if front matter block was empty */
|
|
55
|
+
isEmpty: boolean;
|
|
56
|
+
/** The original content if isEmpty is true */
|
|
57
|
+
empty?: string;
|
|
58
|
+
/** File path (set by matter.read) */
|
|
59
|
+
path?: string;
|
|
60
|
+
/** Stringify the file back to a string */
|
|
61
|
+
stringify: (data?: Record<string, unknown>, options?: GrayMatterOptions) => string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Input that can be passed to gray-matter
|
|
66
|
+
*/
|
|
67
|
+
export type GrayMatterInput = string | Buffer | { content: string; data?: Record<string, unknown> };
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The matter function interface with static methods
|
|
71
|
+
*/
|
|
72
|
+
export interface MatterFunction {
|
|
73
|
+
(input: GrayMatterInput, options?: GrayMatterOptions): GrayMatterFile;
|
|
74
|
+
stringify: (
|
|
75
|
+
file: GrayMatterFile | string,
|
|
76
|
+
data?: Record<string, unknown>,
|
|
77
|
+
options?: GrayMatterOptions,
|
|
78
|
+
) => string;
|
|
79
|
+
read: (filepath: string, options?: GrayMatterOptions) => GrayMatterFile;
|
|
80
|
+
test: (str: string, options?: GrayMatterOptions) => boolean;
|
|
81
|
+
language: (str: string, options?: GrayMatterOptions) => { raw: string; name: string };
|
|
82
|
+
clearCache: () => void;
|
|
83
|
+
cache: Map<string, GrayMatterFile>;
|
|
84
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Strip BOM (Byte Order Mark) from a string
|
|
5
|
+
*/
|
|
6
|
+
function stripBom(str: string): string {
|
|
7
|
+
return str.charCodeAt(0) === 0xfeff ? str.slice(1) : str;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true if `val` is a Buffer
|
|
12
|
+
*/
|
|
13
|
+
function isBuffer(val: unknown): val is Buffer {
|
|
14
|
+
return Buffer.isBuffer(val);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns true if `val` is a plain object (not a Buffer or other special object)
|
|
19
|
+
*/
|
|
20
|
+
export function isObject(val: unknown): val is Record<string, unknown> {
|
|
21
|
+
return typeof val === "object" && val !== null && !Array.isArray(val) && !Buffer.isBuffer(val);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Cast `input` to a Buffer
|
|
26
|
+
*/
|
|
27
|
+
export function toBuffer(input: string | Buffer): Buffer {
|
|
28
|
+
return typeof input === "string" ? Buffer.from(input) : input;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Cast `input` to a string, stripping BOM
|
|
33
|
+
*/
|
|
34
|
+
export function toString(input: string | Buffer): string {
|
|
35
|
+
if (isBuffer(input)) return stripBom(String(input));
|
|
36
|
+
if (typeof input !== "string") {
|
|
37
|
+
throw new TypeError("expected input to be a string or buffer");
|
|
38
|
+
}
|
|
39
|
+
return stripBom(input);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Cast `val` to an array
|
|
44
|
+
*/
|
|
45
|
+
export function arrayify<T>(val: T | T[] | undefined | null): T[] {
|
|
46
|
+
return val ? (Array.isArray(val) ? val : [val]) : [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Asserts that `val` is a plain object and returns it typed as Record<string, unknown>
|
|
51
|
+
* If `val` is not a plain object, returns an empty object
|
|
52
|
+
*/
|
|
53
|
+
export function toRecord(val: unknown): Record<string, unknown> {
|
|
54
|
+
return isObject(val) ? val : {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get a string property from an object with a default value
|
|
59
|
+
*/
|
|
60
|
+
export function getStringProp(obj: unknown, key: string, defaultValue = ""): string {
|
|
61
|
+
if (!isObject(obj)) return defaultValue;
|
|
62
|
+
const value = obj[key];
|
|
63
|
+
return typeof value === "string" ? value : defaultValue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (import.meta.vitest) {
|
|
67
|
+
describe("utils", () => {
|
|
68
|
+
describe("toRecord", () => {
|
|
69
|
+
it("should return object as-is", () => {
|
|
70
|
+
const obj = { a: 1, b: "hello" };
|
|
71
|
+
expect(toRecord(obj)).toBe(obj);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should return empty object for non-objects", () => {
|
|
75
|
+
expect(toRecord(null)).toEqual({});
|
|
76
|
+
expect(toRecord(undefined)).toEqual({});
|
|
77
|
+
expect(toRecord("string")).toEqual({});
|
|
78
|
+
expect(toRecord(123)).toEqual({});
|
|
79
|
+
expect(toRecord([])).toEqual({});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("getStringProp", () => {
|
|
84
|
+
it("should return string property value", () => {
|
|
85
|
+
expect(getStringProp({ name: "test" }, "name")).toBe("test");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should return default for missing property", () => {
|
|
89
|
+
expect(getStringProp({ other: "value" }, "name")).toBe("");
|
|
90
|
+
expect(getStringProp({ other: "value" }, "name", "default")).toBe("default");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should return default for non-string property", () => {
|
|
94
|
+
expect(getStringProp({ count: 42 }, "count")).toBe("");
|
|
95
|
+
expect(getStringProp({ flag: true }, "flag")).toBe("");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should return default for non-objects", () => {
|
|
99
|
+
expect(getStringProp(null, "name")).toBe("");
|
|
100
|
+
expect(getStringProp("string", "name")).toBe("");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("stripBom", () => {
|
|
105
|
+
it("should strip BOM from string", () => {
|
|
106
|
+
expect(stripBom("\uFEFFhello")).toBe("hello");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should return string unchanged if no BOM", () => {
|
|
110
|
+
expect(stripBom("hello")).toBe("hello");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("isBuffer", () => {
|
|
115
|
+
it("should return true for Buffer", () => {
|
|
116
|
+
expect(isBuffer(Buffer.from("test"))).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should return false for string", () => {
|
|
120
|
+
expect(isBuffer("test")).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("isObject", () => {
|
|
125
|
+
it("should return true for plain object", () => {
|
|
126
|
+
expect(isObject({})).toBe(true);
|
|
127
|
+
expect(isObject({ a: 1 })).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should return false for array", () => {
|
|
131
|
+
expect(isObject([])).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should return false for null", () => {
|
|
135
|
+
expect(isObject(null)).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should return false for Buffer", () => {
|
|
139
|
+
expect(isObject(Buffer.from("test"))).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("arrayify", () => {
|
|
144
|
+
it("should wrap non-array in array", () => {
|
|
145
|
+
expect(arrayify("test")).toEqual(["test"]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should return array unchanged", () => {
|
|
149
|
+
expect(arrayify(["a", "b"])).toEqual(["a", "b"]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should return empty array for null/undefined", () => {
|
|
153
|
+
expect(arrayify(null)).toEqual([]);
|
|
154
|
+
expect(arrayify(undefined)).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|