gray-matter-es 0.1.2 → 0.2.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/README.md +75 -21
- package/dist/index.d.mts +40 -23
- package/dist/index.mjs +45 -54
- package/package.json +1 -1
- package/src/excerpt.ts +1 -2
- package/src/index.ts +74 -114
- package/src/stringify.ts +7 -7
- package/src/to-file.ts +23 -25
- package/src/types.ts +6 -22
- package/src/utils.ts +21 -18
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ ESM-only [gray-matter](https://github.com/jonschlinkert/gray-matter) implementat
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
10
|
- 🚀 ESM-only, no CommonJS
|
|
11
|
+
- 🌲 Tree-shakable (named exports)
|
|
12
|
+
- 🌐 Browser-compatible (no Node.js dependencies)
|
|
11
13
|
- 📦 Zero runtime dependencies (YAML parser bundled from [@std/yaml](https://jsr.io/@std/yaml))
|
|
12
14
|
- 🔷 Full TypeScript support with strict types
|
|
13
15
|
- ✅ API compatible with gray-matter
|
|
@@ -23,12 +25,10 @@ pnpm add gray-matter-es
|
|
|
23
25
|
## Usage
|
|
24
26
|
|
|
25
27
|
```typescript
|
|
26
|
-
import matter from "gray-matter-es";
|
|
27
|
-
// or
|
|
28
|
-
import { matter } from "gray-matter-es";
|
|
28
|
+
import * as matter from "gray-matter-es";
|
|
29
29
|
|
|
30
30
|
// Parse front matter
|
|
31
|
-
const file = matter("---\ntitle: Hello\n---\nThis is content");
|
|
31
|
+
const file = matter.matter("---\ntitle: Hello\n---\nThis is content");
|
|
32
32
|
console.log(file.data); // { title: 'Hello' }
|
|
33
33
|
console.log(file.content); // 'This is content'
|
|
34
34
|
|
|
@@ -39,9 +39,6 @@ const str = matter.stringify("content", { title: "Hello" });
|
|
|
39
39
|
// ---
|
|
40
40
|
// content
|
|
41
41
|
|
|
42
|
-
// Read from file (Node.js)
|
|
43
|
-
const fileFromDisk = matter.read("./post.md");
|
|
44
|
-
|
|
45
42
|
// Test if string has front matter
|
|
46
43
|
matter.test("---\ntitle: Hello\n---"); // true
|
|
47
44
|
|
|
@@ -49,6 +46,12 @@ matter.test("---\ntitle: Hello\n---"); // true
|
|
|
49
46
|
matter.language("---json\n{}\n---"); // { raw: 'json', name: 'json' }
|
|
50
47
|
```
|
|
51
48
|
|
|
49
|
+
You can also import individual functions for tree-shaking:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { matter, stringify, test, language, clearCache, cache } from "gray-matter-es";
|
|
53
|
+
```
|
|
54
|
+
|
|
52
55
|
### Custom Delimiters
|
|
53
56
|
|
|
54
57
|
```typescript
|
|
@@ -78,11 +81,11 @@ console.log(file.excerpt); // 'excerpt\n'
|
|
|
78
81
|
|
|
79
82
|
### `matter(input, options?)`
|
|
80
83
|
|
|
81
|
-
Parse front matter from a string or
|
|
84
|
+
Parse front matter from a string or Uint8Array.
|
|
82
85
|
|
|
83
86
|
**Parameters:**
|
|
84
87
|
|
|
85
|
-
- `input` - String,
|
|
88
|
+
- `input` - String, Uint8Array, or object with `content` property
|
|
86
89
|
- `options` - Optional configuration
|
|
87
90
|
|
|
88
91
|
**Returns:** `GrayMatterFile` object with:
|
|
@@ -90,28 +93,32 @@ Parse front matter from a string or buffer.
|
|
|
90
93
|
- `data` - Parsed front matter data
|
|
91
94
|
- `content` - Content after front matter
|
|
92
95
|
- `excerpt` - Extracted excerpt (if enabled)
|
|
93
|
-
- `orig` - Original input as
|
|
96
|
+
- `orig` - Original input as Uint8Array
|
|
94
97
|
- `language` - Detected/specified language
|
|
95
98
|
- `matter` - Raw front matter string
|
|
96
99
|
- `isEmpty` - True if front matter block was empty
|
|
97
100
|
- `stringify(data?, options?)` - Stringify the file back
|
|
98
101
|
|
|
99
|
-
### `
|
|
102
|
+
### `stringify(file, data?, options?)`
|
|
100
103
|
|
|
101
104
|
Stringify data to front matter and append content.
|
|
102
105
|
|
|
103
|
-
### `
|
|
104
|
-
|
|
105
|
-
Synchronously read and parse a file.
|
|
106
|
-
|
|
107
|
-
### `matter.test(str, options?)`
|
|
106
|
+
### `test(str, options?)`
|
|
108
107
|
|
|
109
108
|
Test if a string has front matter.
|
|
110
109
|
|
|
111
|
-
### `
|
|
110
|
+
### `language(str, options?)`
|
|
112
111
|
|
|
113
112
|
Detect the language specified after the opening delimiter.
|
|
114
113
|
|
|
114
|
+
### `clearCache()`
|
|
115
|
+
|
|
116
|
+
Clear the internal cache.
|
|
117
|
+
|
|
118
|
+
### `cache`
|
|
119
|
+
|
|
120
|
+
The internal cache (read-only access).
|
|
121
|
+
|
|
115
122
|
### Options
|
|
116
123
|
|
|
117
124
|
- `language` - Language to use for parsing (default: `'yaml'`)
|
|
@@ -123,7 +130,9 @@ Detect the language specified after the opening delimiter.
|
|
|
123
130
|
## Differences from gray-matter
|
|
124
131
|
|
|
125
132
|
- ESM-only (no CommonJS support)
|
|
133
|
+
- Browser-compatible (no Node.js dependencies)
|
|
126
134
|
- Uses [`@std/yaml`](https://jsr.io/@std/yaml) instead of `js-yaml`
|
|
135
|
+
- Removed `matter.read()` (use your own file reading)
|
|
127
136
|
- Removed JavaScript front matter engine (security: avoids `eval`)
|
|
128
137
|
- Removed deprecated options (`lang`, `delims`, `parsers`)
|
|
129
138
|
- Removed `section-matter` support
|
|
@@ -135,9 +144,25 @@ Detect the language specified after the opening delimiter.
|
|
|
135
144
|
|
|
136
145
|
```diff
|
|
137
146
|
- const matter = require('gray-matter');
|
|
138
|
-
+ import matter from 'gray-matter-es';
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
+ import * as matter from 'gray-matter-es';
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### API Changes
|
|
151
|
+
|
|
152
|
+
The main function is now `matter.matter()` instead of `matter()`:
|
|
153
|
+
|
|
154
|
+
```diff
|
|
155
|
+
- const result = matter(str);
|
|
156
|
+
+ const result = matter.matter(str);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Other methods remain the same:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
matter.stringify(file, data);
|
|
163
|
+
matter.test(str);
|
|
164
|
+
matter.language(str);
|
|
165
|
+
matter.clearCache();
|
|
141
166
|
```
|
|
142
167
|
|
|
143
168
|
### Removed Features
|
|
@@ -191,7 +216,36 @@ If you're using CommonJS, you'll need to either:
|
|
|
191
216
|
2. Use dynamic import:
|
|
192
217
|
|
|
193
218
|
```javascript
|
|
194
|
-
const matter = await import("gray-matter-es")
|
|
219
|
+
const { matter } = await import("gray-matter-es");
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
### Prerequisites
|
|
225
|
+
|
|
226
|
+
This project uses [Nix](https://nixos.org/) for development environment management. Make sure you have Nix installed with flakes enabled.
|
|
227
|
+
|
|
228
|
+
### Setup
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Enter the development shell
|
|
232
|
+
nix develop
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Commands
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Run tests
|
|
239
|
+
pnpm run test
|
|
240
|
+
|
|
241
|
+
# Type check with tsgo
|
|
242
|
+
pnpm run typecheck
|
|
243
|
+
|
|
244
|
+
# Build
|
|
245
|
+
pnpm run build
|
|
246
|
+
|
|
247
|
+
# Lint
|
|
248
|
+
pnpm run lint
|
|
195
249
|
```
|
|
196
250
|
|
|
197
251
|
## License
|
package/dist/index.d.mts
CHANGED
|
@@ -43,8 +43,8 @@ interface GrayMatterFile {
|
|
|
43
43
|
content: string;
|
|
44
44
|
/** The extracted excerpt (if enabled) */
|
|
45
45
|
excerpt: string;
|
|
46
|
-
/** The original input as a
|
|
47
|
-
orig:
|
|
46
|
+
/** The original input as a Uint8Array */
|
|
47
|
+
orig: Uint8Array;
|
|
48
48
|
/** The detected/specified language */
|
|
49
49
|
language: string;
|
|
50
50
|
/** The raw front matter string (without delimiters) */
|
|
@@ -53,33 +53,16 @@ interface GrayMatterFile {
|
|
|
53
53
|
isEmpty: boolean;
|
|
54
54
|
/** The original content if isEmpty is true */
|
|
55
55
|
empty?: string;
|
|
56
|
-
/** File path (set by matter.read) */
|
|
57
|
-
path?: string;
|
|
58
56
|
/** Stringify the file back to a string */
|
|
59
57
|
stringify: (data?: Record<string, unknown>, options?: GrayMatterOptions) => string;
|
|
60
58
|
}
|
|
61
59
|
/**
|
|
62
60
|
* Input that can be passed to gray-matter
|
|
63
61
|
*/
|
|
64
|
-
type GrayMatterInput = string |
|
|
62
|
+
type GrayMatterInput = string | Uint8Array | {
|
|
65
63
|
content: string;
|
|
66
64
|
data?: Record<string, unknown>;
|
|
67
65
|
};
|
|
68
|
-
/**
|
|
69
|
-
* The matter function interface with static methods
|
|
70
|
-
*/
|
|
71
|
-
interface MatterFunction {
|
|
72
|
-
(input: GrayMatterInput, options?: GrayMatterOptions): GrayMatterFile;
|
|
73
|
-
stringify: (file: GrayMatterFile | string, data?: Record<string, unknown>, options?: GrayMatterOptions) => string;
|
|
74
|
-
read: (filepath: string, options?: GrayMatterOptions) => GrayMatterFile;
|
|
75
|
-
test: (str: string, options?: GrayMatterOptions) => boolean;
|
|
76
|
-
language: (str: string, options?: GrayMatterOptions) => {
|
|
77
|
-
raw: string;
|
|
78
|
-
name: string;
|
|
79
|
-
};
|
|
80
|
-
clearCache: () => void;
|
|
81
|
-
cache: Map<string, GrayMatterFile>;
|
|
82
|
-
}
|
|
83
66
|
//#endregion
|
|
84
67
|
//#region src/engines.d.ts
|
|
85
68
|
/**
|
|
@@ -89,8 +72,42 @@ type BuiltinLanguage = "yaml" | "json";
|
|
|
89
72
|
//#endregion
|
|
90
73
|
//#region src/index.d.ts
|
|
91
74
|
/**
|
|
92
|
-
*
|
|
75
|
+
* Cache for parsed results
|
|
76
|
+
*/
|
|
77
|
+
declare const cache: Map<string, GrayMatterFile>;
|
|
78
|
+
/**
|
|
79
|
+
* Takes a string or object with `content` property, extracts
|
|
80
|
+
* and parses front-matter from the string, then returns an object
|
|
81
|
+
* with `data`, `content` and other useful properties.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* import { matter } from 'gray-matter-es';
|
|
86
|
+
* console.log(matter('---\ntitle: Home\n---\nOther stuff'));
|
|
87
|
+
* //=> { data: { title: 'Home'}, content: 'Other stuff' }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
declare function matter(input: GrayMatterInput, options?: GrayMatterOptions): GrayMatterFile;
|
|
91
|
+
/**
|
|
92
|
+
* Clear the cache
|
|
93
|
+
*/
|
|
94
|
+
declare function clearCache(): void;
|
|
95
|
+
/**
|
|
96
|
+
* Detect the language to use, if one is defined after the
|
|
97
|
+
* first front-matter delimiter.
|
|
98
|
+
*/
|
|
99
|
+
declare function language(str: string, options?: GrayMatterOptions): {
|
|
100
|
+
raw: string;
|
|
101
|
+
name: string;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Returns true if the given string has front matter.
|
|
105
|
+
*/
|
|
106
|
+
declare function test(str: string, options?: GrayMatterOptions): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Stringify an object to YAML or the specified language, and
|
|
109
|
+
* append it to the given string.
|
|
93
110
|
*/
|
|
94
|
-
declare
|
|
111
|
+
declare function stringify(file: GrayMatterFile | string, data?: Record<string, unknown>, options?: GrayMatterOptions): string;
|
|
95
112
|
//#endregion
|
|
96
|
-
export { type BuiltinLanguage, type Engine, type GrayMatterFile, type GrayMatterInput, type GrayMatterOptions, type
|
|
113
|
+
export { type BuiltinLanguage, type Engine, type GrayMatterFile, type GrayMatterInput, type GrayMatterOptions, type ResolvedOptions, cache, clearCache, language, matter, stringify, test };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { Buffer } from "node:buffer";
|
|
3
|
-
|
|
4
1
|
//#region src/utils.ts
|
|
2
|
+
const textEncoder = new TextEncoder();
|
|
3
|
+
const textDecoder = new TextDecoder();
|
|
5
4
|
/**
|
|
6
5
|
* Strip BOM (Byte Order Mark) from a string
|
|
7
6
|
*/
|
|
@@ -9,29 +8,29 @@ function stripBom(str$1) {
|
|
|
9
8
|
return str$1.charCodeAt(0) === 65279 ? str$1.slice(1) : str$1;
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
|
-
* Returns true if `val` is a
|
|
11
|
+
* Returns true if `val` is a Uint8Array
|
|
13
12
|
*/
|
|
14
|
-
function
|
|
15
|
-
return
|
|
13
|
+
function isUint8Array(val) {
|
|
14
|
+
return val instanceof Uint8Array;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* Returns true if `val` is a plain object (not a
|
|
17
|
+
* Returns true if `val` is a plain object (not a Uint8Array or other special object)
|
|
19
18
|
*/
|
|
20
19
|
function isObject$1(val) {
|
|
21
|
-
return typeof val === "object" && val !== null && !Array.isArray(val) && !
|
|
20
|
+
return typeof val === "object" && val !== null && !Array.isArray(val) && !(val instanceof Uint8Array);
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
|
-
* Cast `input` to a
|
|
23
|
+
* Cast `input` to a Uint8Array
|
|
25
24
|
*/
|
|
26
|
-
function
|
|
27
|
-
return typeof input === "string" ?
|
|
25
|
+
function toUint8Array(input) {
|
|
26
|
+
return typeof input === "string" ? textEncoder.encode(input) : input;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
29
|
* Cast `input` to a string, stripping BOM
|
|
31
30
|
*/
|
|
32
31
|
function toString(input) {
|
|
33
|
-
if (
|
|
34
|
-
if (typeof input !== "string") throw new TypeError("expected input to be a string or
|
|
32
|
+
if (isUint8Array(input)) return stripBom(textDecoder.decode(input));
|
|
33
|
+
if (typeof input !== "string") throw new TypeError("expected input to be a string or Uint8Array");
|
|
35
34
|
return stripBom(input);
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
@@ -2104,7 +2103,7 @@ var DumperState = class {
|
|
|
2104
2103
|
* @param data The data to serialize.
|
|
2105
2104
|
* @param options The options for serialization.
|
|
2106
2105
|
* @returns A YAML string.
|
|
2107
|
-
*/ function stringify$
|
|
2106
|
+
*/ function stringify$2(data, options = {}) {
|
|
2108
2107
|
return new DumperState({
|
|
2109
2108
|
...options,
|
|
2110
2109
|
schema: SCHEMA_MAP.get(options.schema)
|
|
@@ -2137,7 +2136,7 @@ const yaml = {
|
|
|
2137
2136
|
return toRecord(parse$1(str$1));
|
|
2138
2137
|
},
|
|
2139
2138
|
stringify: (data) => {
|
|
2140
|
-
return stringify$
|
|
2139
|
+
return stringify$2(data);
|
|
2141
2140
|
}
|
|
2142
2141
|
};
|
|
2143
2142
|
/**
|
|
@@ -2159,11 +2158,11 @@ const json = {
|
|
|
2159
2158
|
/**
|
|
2160
2159
|
* Get engine by language name
|
|
2161
2160
|
*/
|
|
2162
|
-
function getEngine(language) {
|
|
2163
|
-
switch (language) {
|
|
2161
|
+
function getEngine(language$1) {
|
|
2162
|
+
switch (language$1) {
|
|
2164
2163
|
case "yaml": return yaml;
|
|
2165
2164
|
case "json": return json;
|
|
2166
|
-
default: throw new Error(`Unknown language: ${language}`);
|
|
2165
|
+
default: throw new Error(`Unknown language: ${language$1}`);
|
|
2167
2166
|
}
|
|
2168
2167
|
}
|
|
2169
2168
|
|
|
@@ -2193,8 +2192,8 @@ function excerpt(file, options) {
|
|
|
2193
2192
|
/**
|
|
2194
2193
|
* Parse front matter string using the specified language engine
|
|
2195
2194
|
*/
|
|
2196
|
-
function parse(language, str$1) {
|
|
2197
|
-
return getEngine(language).parse(str$1);
|
|
2195
|
+
function parse(language$1, str$1) {
|
|
2196
|
+
return getEngine(language$1).parse(str$1);
|
|
2198
2197
|
}
|
|
2199
2198
|
|
|
2200
2199
|
//#endregion
|
|
@@ -2214,7 +2213,7 @@ function newline(str$1) {
|
|
|
2214
2213
|
/**
|
|
2215
2214
|
* Stringify file object to string with front matter
|
|
2216
2215
|
*/
|
|
2217
|
-
function stringify(file, data, options) {
|
|
2216
|
+
function stringify$1(file, data, options) {
|
|
2218
2217
|
if (data == null && options == null) if (isGrayMatterFile(file)) {
|
|
2219
2218
|
data = file.data;
|
|
2220
2219
|
options = {};
|
|
@@ -2265,19 +2264,19 @@ function toFile(input) {
|
|
|
2265
2264
|
const normalized = normalizeInput(input);
|
|
2266
2265
|
const data = isObject$1(normalized.data) ? normalized.data : {};
|
|
2267
2266
|
const content = toString(normalized.content ?? "");
|
|
2268
|
-
const language = normalized.language ?? "";
|
|
2267
|
+
const language$1 = normalized.language ?? "";
|
|
2269
2268
|
const matter$1 = normalized.matter ?? "";
|
|
2270
2269
|
return {
|
|
2271
2270
|
data,
|
|
2272
2271
|
content,
|
|
2273
2272
|
excerpt: "",
|
|
2274
|
-
orig:
|
|
2275
|
-
language,
|
|
2273
|
+
orig: toUint8Array(normalized.content ?? ""),
|
|
2274
|
+
language: language$1,
|
|
2276
2275
|
matter: matter$1,
|
|
2277
2276
|
isEmpty: false,
|
|
2278
2277
|
stringify(newData, options) {
|
|
2279
2278
|
if (options?.language) this.language = options.language;
|
|
2280
|
-
return stringify(this, newData, options);
|
|
2279
|
+
return stringify$1(this, newData, options);
|
|
2281
2280
|
}
|
|
2282
2281
|
};
|
|
2283
2282
|
}
|
|
@@ -2295,12 +2294,12 @@ const cache = /* @__PURE__ */ new Map();
|
|
|
2295
2294
|
*
|
|
2296
2295
|
* @example
|
|
2297
2296
|
* ```ts
|
|
2298
|
-
* import matter from 'gray-matter-es';
|
|
2297
|
+
* import { matter } from 'gray-matter-es';
|
|
2299
2298
|
* console.log(matter('---\ntitle: Home\n---\nOther stuff'));
|
|
2300
2299
|
* //=> { data: { title: 'Home'}, content: 'Other stuff' }
|
|
2301
2300
|
* ```
|
|
2302
2301
|
*/
|
|
2303
|
-
function
|
|
2302
|
+
function matter(input, options) {
|
|
2304
2303
|
if (input === "") return {
|
|
2305
2304
|
...toFile(input),
|
|
2306
2305
|
isEmpty: true
|
|
@@ -2318,6 +2317,12 @@ function matterImpl(input, options) {
|
|
|
2318
2317
|
return parseMatter(file, options);
|
|
2319
2318
|
}
|
|
2320
2319
|
/**
|
|
2320
|
+
* Clear the cache
|
|
2321
|
+
*/
|
|
2322
|
+
function clearCache() {
|
|
2323
|
+
cache.clear();
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2321
2326
|
* Parse front matter from file
|
|
2322
2327
|
*/
|
|
2323
2328
|
function parseMatter(file, options) {
|
|
@@ -2334,7 +2339,7 @@ function parseMatter(file, options) {
|
|
|
2334
2339
|
if (str$1.at(openLen) === open.at(-1)) return file;
|
|
2335
2340
|
str$1 = str$1.slice(openLen);
|
|
2336
2341
|
const len = str$1.length;
|
|
2337
|
-
const lang =
|
|
2342
|
+
const lang = language(str$1, opts);
|
|
2338
2343
|
if (lang.name) {
|
|
2339
2344
|
file.language = lang.name;
|
|
2340
2345
|
str$1 = str$1.slice(lang.raw.length);
|
|
@@ -2360,43 +2365,29 @@ function parseMatter(file, options) {
|
|
|
2360
2365
|
* Detect the language to use, if one is defined after the
|
|
2361
2366
|
* first front-matter delimiter.
|
|
2362
2367
|
*/
|
|
2363
|
-
function
|
|
2368
|
+
function language(str$1, options) {
|
|
2364
2369
|
const opts = defaults(options);
|
|
2365
2370
|
const open = opts.delimiters[0];
|
|
2366
|
-
if (
|
|
2367
|
-
const
|
|
2371
|
+
if (test(str$1, opts)) str$1 = str$1.slice(open.length);
|
|
2372
|
+
const lang = str$1.slice(0, str$1.search(/\r?\n/));
|
|
2368
2373
|
return {
|
|
2369
|
-
raw:
|
|
2370
|
-
name:
|
|
2374
|
+
raw: lang,
|
|
2375
|
+
name: lang ? lang.trim() : ""
|
|
2371
2376
|
};
|
|
2372
2377
|
}
|
|
2373
2378
|
/**
|
|
2374
2379
|
* Returns true if the given string has front matter.
|
|
2375
2380
|
*/
|
|
2376
|
-
function
|
|
2381
|
+
function test(str$1, options) {
|
|
2377
2382
|
return str$1.startsWith(defaults(options).delimiters[0]);
|
|
2378
2383
|
}
|
|
2379
2384
|
/**
|
|
2380
|
-
*
|
|
2385
|
+
* Stringify an object to YAML or the specified language, and
|
|
2386
|
+
* append it to the given string.
|
|
2381
2387
|
*/
|
|
2382
|
-
|
|
2383
|
-
stringify
|
|
2384
|
-
|
|
2385
|
-
return stringify(file, data, options);
|
|
2386
|
-
},
|
|
2387
|
-
read: (filepath, options) => {
|
|
2388
|
-
const file = matterImpl(readFileSync(filepath, "utf8"), options);
|
|
2389
|
-
file.path = filepath;
|
|
2390
|
-
return file;
|
|
2391
|
-
},
|
|
2392
|
-
test: matterTest,
|
|
2393
|
-
language: matterLanguage,
|
|
2394
|
-
clearCache: () => {
|
|
2395
|
-
cache.clear();
|
|
2396
|
-
},
|
|
2397
|
-
cache
|
|
2398
|
-
});
|
|
2399
|
-
var src_default = matter;
|
|
2388
|
+
function stringify(file, data, options) {
|
|
2389
|
+
return stringify$1(typeof file === "string" ? matter(file, options) : file, data, options);
|
|
2390
|
+
}
|
|
2400
2391
|
|
|
2401
2392
|
//#endregion
|
|
2402
|
-
export {
|
|
2393
|
+
export { cache, clearCache, language, matter, stringify, test };
|
package/package.json
CHANGED
package/src/excerpt.ts
CHANGED
|
@@ -36,13 +36,12 @@ export function excerpt(file: GrayMatterFile, options?: GrayMatterOptions): Gray
|
|
|
36
36
|
|
|
37
37
|
if (import.meta.vitest) {
|
|
38
38
|
const { fc, test } = await import("@fast-check/vitest");
|
|
39
|
-
const { Buffer } = await import("node:buffer");
|
|
40
39
|
|
|
41
40
|
const makeFile = (content: string, data: Record<string, unknown> = {}): GrayMatterFile => ({
|
|
42
41
|
content,
|
|
43
42
|
data,
|
|
44
43
|
excerpt: "",
|
|
45
|
-
orig:
|
|
44
|
+
orig: new TextEncoder().encode(content),
|
|
46
45
|
language: "yaml",
|
|
47
46
|
matter: "",
|
|
48
47
|
isEmpty: false,
|
package/src/index.ts
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { defaults } from "./defaults.ts";
|
|
3
2
|
import { toBuiltinLanguage } from "./engines.ts";
|
|
4
3
|
import { excerpt } from "./excerpt.ts";
|
|
5
4
|
import { parse } from "./parse.ts";
|
|
6
|
-
import { stringify } from "./stringify.ts";
|
|
5
|
+
import { stringify as stringifyImpl } from "./stringify.ts";
|
|
7
6
|
import { toFile } from "./to-file.ts";
|
|
8
|
-
import type {
|
|
9
|
-
GrayMatterFile,
|
|
10
|
-
GrayMatterInput,
|
|
11
|
-
GrayMatterOptions,
|
|
12
|
-
MatterFunction,
|
|
13
|
-
} from "./types.ts";
|
|
7
|
+
import type { GrayMatterFile, GrayMatterInput, GrayMatterOptions } from "./types.ts";
|
|
14
8
|
|
|
15
9
|
export type {
|
|
16
10
|
Engine,
|
|
17
11
|
GrayMatterFile,
|
|
18
12
|
GrayMatterInput,
|
|
19
13
|
GrayMatterOptions,
|
|
20
|
-
MatterFunction,
|
|
21
14
|
ResolvedOptions,
|
|
22
15
|
} from "./types.ts";
|
|
23
16
|
|
|
@@ -26,7 +19,7 @@ export type { BuiltinLanguage } from "./engines.ts";
|
|
|
26
19
|
/**
|
|
27
20
|
* Cache for parsed results
|
|
28
21
|
*/
|
|
29
|
-
const cache = new Map<string, GrayMatterFile>();
|
|
22
|
+
const cache: Map<string, GrayMatterFile> = new Map<string, GrayMatterFile>();
|
|
30
23
|
|
|
31
24
|
/**
|
|
32
25
|
* Takes a string or object with `content` property, extracts
|
|
@@ -35,12 +28,12 @@ const cache = new Map<string, GrayMatterFile>();
|
|
|
35
28
|
*
|
|
36
29
|
* @example
|
|
37
30
|
* ```ts
|
|
38
|
-
* import matter from 'gray-matter-es';
|
|
31
|
+
* import { matter } from 'gray-matter-es';
|
|
39
32
|
* console.log(matter('---\ntitle: Home\n---\nOther stuff'));
|
|
40
33
|
* //=> { data: { title: 'Home'}, content: 'Other stuff' }
|
|
41
34
|
* ```
|
|
42
35
|
*/
|
|
43
|
-
function
|
|
36
|
+
function matter(input: GrayMatterInput, options?: GrayMatterOptions): GrayMatterFile {
|
|
44
37
|
if (input === "") {
|
|
45
38
|
return { ...toFile(input), isEmpty: true };
|
|
46
39
|
}
|
|
@@ -62,6 +55,13 @@ function matterImpl(input: GrayMatterInput, options?: GrayMatterOptions): GrayMa
|
|
|
62
55
|
return parseMatter(file, options);
|
|
63
56
|
}
|
|
64
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Clear the cache
|
|
60
|
+
*/
|
|
61
|
+
function clearCache(): void {
|
|
62
|
+
cache.clear();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
65
|
/**
|
|
66
66
|
* Parse front matter from file
|
|
67
67
|
*/
|
|
@@ -94,7 +94,7 @@ function parseMatter(file: GrayMatterFile, options?: GrayMatterOptions): GrayMat
|
|
|
94
94
|
const len = str.length;
|
|
95
95
|
|
|
96
96
|
// use the language defined after first delimiter, if it exists
|
|
97
|
-
const lang =
|
|
97
|
+
const lang = language(str, opts);
|
|
98
98
|
if (lang.name) {
|
|
99
99
|
file.language = lang.name;
|
|
100
100
|
str = str.slice(lang.raw.length);
|
|
@@ -140,89 +140,49 @@ function parseMatter(file: GrayMatterFile, options?: GrayMatterOptions): GrayMat
|
|
|
140
140
|
* Detect the language to use, if one is defined after the
|
|
141
141
|
* first front-matter delimiter.
|
|
142
142
|
*/
|
|
143
|
-
function
|
|
143
|
+
function language(str: string, options?: GrayMatterOptions): { raw: string; name: string } {
|
|
144
144
|
const opts = defaults(options);
|
|
145
145
|
const open = opts.delimiters[0];
|
|
146
146
|
|
|
147
|
-
if (
|
|
147
|
+
if (test(str, opts)) {
|
|
148
148
|
str = str.slice(open.length);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
const
|
|
151
|
+
const lang = str.slice(0, str.search(/\r?\n/));
|
|
152
152
|
return {
|
|
153
|
-
raw:
|
|
154
|
-
name:
|
|
153
|
+
raw: lang,
|
|
154
|
+
name: lang ? lang.trim() : "",
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
159
|
* Returns true if the given string has front matter.
|
|
160
160
|
*/
|
|
161
|
-
function
|
|
161
|
+
function test(str: string, options?: GrayMatterOptions): boolean {
|
|
162
162
|
return str.startsWith(defaults(options).delimiters[0]);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
|
-
*
|
|
166
|
+
* Stringify an object to YAML or the specified language, and
|
|
167
|
+
* append it to the given string.
|
|
167
168
|
*/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (typeof file === "string") file = matterImpl(file, options);
|
|
179
|
-
return stringify(file, data, options);
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Synchronously read a file from the file system and parse front matter.
|
|
184
|
-
*/
|
|
185
|
-
read: (filepath: string, options?: GrayMatterOptions): GrayMatterFile => {
|
|
186
|
-
const str = readFileSync(filepath, "utf8");
|
|
187
|
-
const file = matterImpl(str, options);
|
|
188
|
-
file.path = filepath;
|
|
189
|
-
return file;
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Returns true if the given string has front matter.
|
|
194
|
-
*/
|
|
195
|
-
test: matterTest,
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Detect the language to use, if one is defined after the
|
|
199
|
-
* first front-matter delimiter.
|
|
200
|
-
*/
|
|
201
|
-
language: matterLanguage,
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Clear the cache
|
|
205
|
-
*/
|
|
206
|
-
clearCache: (): void => {
|
|
207
|
-
cache.clear();
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Expose cache (read-only access)
|
|
212
|
-
*/
|
|
213
|
-
cache,
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
export { matter };
|
|
217
|
-
export default matter;
|
|
169
|
+
function stringify(
|
|
170
|
+
file: GrayMatterFile | string,
|
|
171
|
+
data?: Record<string, unknown>,
|
|
172
|
+
options?: GrayMatterOptions,
|
|
173
|
+
): string {
|
|
174
|
+
const resolvedFile = typeof file === "string" ? matter(file, options) : file;
|
|
175
|
+
return stringifyImpl(resolvedFile, data, options);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { matter, stringify, test, language, clearCache, cache };
|
|
218
179
|
|
|
219
180
|
if (import.meta.vitest) {
|
|
220
|
-
const { fc, test } = await import("@fast-check/vitest");
|
|
221
|
-
const { Buffer } = await import("node:buffer");
|
|
181
|
+
const { fc, test: fcTest } = await import("@fast-check/vitest");
|
|
222
182
|
|
|
223
183
|
describe("matter", () => {
|
|
224
184
|
beforeEach(() => {
|
|
225
|
-
|
|
185
|
+
clearCache();
|
|
226
186
|
});
|
|
227
187
|
|
|
228
188
|
it("should extract YAML front matter", () => {
|
|
@@ -298,9 +258,9 @@ if (import.meta.vitest) {
|
|
|
298
258
|
});
|
|
299
259
|
});
|
|
300
260
|
|
|
301
|
-
describe("
|
|
261
|
+
describe("stringify", () => {
|
|
302
262
|
it("should stringify data to YAML front matter", () => {
|
|
303
|
-
const result =
|
|
263
|
+
const result = stringify("content", { title: "Hello" });
|
|
304
264
|
expect(result).toContain("---");
|
|
305
265
|
expect(result).toContain("title: Hello");
|
|
306
266
|
expect(result).toContain("content");
|
|
@@ -308,36 +268,36 @@ if (import.meta.vitest) {
|
|
|
308
268
|
|
|
309
269
|
it("should stringify file object", () => {
|
|
310
270
|
const file = matter("---\ntitle: Test\n---\ncontent");
|
|
311
|
-
const result =
|
|
271
|
+
const result = stringify(file, { title: "Updated" });
|
|
312
272
|
expect(result).toContain("title: Updated");
|
|
313
273
|
});
|
|
314
274
|
});
|
|
315
275
|
|
|
316
|
-
describe("
|
|
276
|
+
describe("test", () => {
|
|
317
277
|
it("should return true for string with front matter", () => {
|
|
318
|
-
expect(
|
|
278
|
+
expect(test("---\nabc: xyz\n---")).toBe(true);
|
|
319
279
|
});
|
|
320
280
|
|
|
321
281
|
it("should return false for string without front matter", () => {
|
|
322
|
-
expect(
|
|
282
|
+
expect(test("foo bar")).toBe(false);
|
|
323
283
|
});
|
|
324
284
|
});
|
|
325
285
|
|
|
326
|
-
describe("
|
|
286
|
+
describe("language", () => {
|
|
327
287
|
it("should detect language after delimiter", () => {
|
|
328
|
-
const result =
|
|
288
|
+
const result = language("---json\n{}\n---");
|
|
329
289
|
expect(result.name).toBe("json");
|
|
330
290
|
});
|
|
331
291
|
|
|
332
292
|
it("should return empty for no language", () => {
|
|
333
|
-
const result =
|
|
293
|
+
const result = language("---\nabc: xyz\n---");
|
|
334
294
|
expect(result.name).toBe("");
|
|
335
295
|
});
|
|
336
296
|
});
|
|
337
297
|
|
|
338
298
|
describe("property-based tests", () => {
|
|
339
299
|
beforeEach(() => {
|
|
340
|
-
|
|
300
|
+
clearCache();
|
|
341
301
|
});
|
|
342
302
|
|
|
343
303
|
/** Arbitrary for YAML-safe keys */
|
|
@@ -355,21 +315,21 @@ if (import.meta.vitest) {
|
|
|
355
315
|
/** Arbitrary for simple YAML-compatible objects */
|
|
356
316
|
const yamlSafeObject = fc.dictionary(yamlKey, yamlSafeValue, { minKeys: 1, maxKeys: 5 });
|
|
357
317
|
|
|
358
|
-
|
|
359
|
-
"
|
|
318
|
+
fcTest.prop([fc.string({ minLength: 1, maxLength: 100 })])(
|
|
319
|
+
"Uint8Array and string input should produce equivalent results",
|
|
360
320
|
(content) => {
|
|
361
|
-
|
|
321
|
+
clearCache();
|
|
362
322
|
const fromString = matter(content);
|
|
363
|
-
|
|
364
|
-
const
|
|
323
|
+
clearCache();
|
|
324
|
+
const fromUint8Array = matter(new TextEncoder().encode(content));
|
|
365
325
|
|
|
366
|
-
expect(fromString.content).toBe(
|
|
367
|
-
expect(fromString.data).toEqual(
|
|
368
|
-
expect(fromString.excerpt).toBe(
|
|
326
|
+
expect(fromString.content).toBe(fromUint8Array.content);
|
|
327
|
+
expect(fromString.data).toEqual(fromUint8Array.data);
|
|
328
|
+
expect(fromString.excerpt).toBe(fromUint8Array.excerpt);
|
|
369
329
|
},
|
|
370
330
|
);
|
|
371
331
|
|
|
372
|
-
|
|
332
|
+
fcTest.prop([yamlSafeObject, fc.string({ minLength: 0, maxLength: 50 })])(
|
|
373
333
|
"parse then stringify should preserve data",
|
|
374
334
|
(data, content) => {
|
|
375
335
|
const frontMatter = Object.entries(data)
|
|
@@ -377,21 +337,21 @@ if (import.meta.vitest) {
|
|
|
377
337
|
.join("\n");
|
|
378
338
|
const input = `---\n${frontMatter}\n---\n${content}`;
|
|
379
339
|
|
|
380
|
-
|
|
340
|
+
clearCache();
|
|
381
341
|
const parsed = matter(input);
|
|
382
|
-
const stringified =
|
|
383
|
-
|
|
342
|
+
const stringified = stringify(parsed);
|
|
343
|
+
clearCache();
|
|
384
344
|
const reparsed = matter(stringified);
|
|
385
345
|
|
|
386
346
|
expect(reparsed.data).toEqual(parsed.data);
|
|
387
347
|
},
|
|
388
348
|
);
|
|
389
349
|
|
|
390
|
-
|
|
350
|
+
fcTest.prop([fc.string({ minLength: 0, maxLength: 100 })])(
|
|
391
351
|
"content without front matter should be preserved",
|
|
392
352
|
(content) => {
|
|
393
353
|
const safeContent = content.replace(/^---/gm, "___");
|
|
394
|
-
|
|
354
|
+
clearCache();
|
|
395
355
|
const result = matter(safeContent);
|
|
396
356
|
|
|
397
357
|
expect(result.content).toBe(safeContent);
|
|
@@ -399,26 +359,26 @@ if (import.meta.vitest) {
|
|
|
399
359
|
},
|
|
400
360
|
);
|
|
401
361
|
|
|
402
|
-
|
|
362
|
+
fcTest.prop([
|
|
403
363
|
yamlSafeObject,
|
|
404
364
|
fc.string({ minLength: 0, maxLength: 50 }),
|
|
405
365
|
fc.string({ minLength: 1, maxLength: 50 }),
|
|
406
|
-
])("
|
|
366
|
+
])("test should correctly detect front matter", (data, content, noFrontMatter) => {
|
|
407
367
|
const frontMatter = Object.entries(data)
|
|
408
368
|
.map(([k, v]) => `${k}: ${typeof v === "string" ? JSON.stringify(v) : v}`)
|
|
409
369
|
.join("\n");
|
|
410
370
|
const withFrontMatter = `---\n${frontMatter}\n---\n${content}`;
|
|
411
371
|
const withoutFrontMatter = noFrontMatter.replace(/^---/gm, "___");
|
|
412
372
|
|
|
413
|
-
expect(
|
|
414
|
-
expect(
|
|
373
|
+
expect(test(withFrontMatter)).toBe(true);
|
|
374
|
+
expect(test(withoutFrontMatter)).toBe(withoutFrontMatter.startsWith("---"));
|
|
415
375
|
});
|
|
416
376
|
|
|
417
|
-
|
|
377
|
+
fcTest.prop([fc.constantFrom("yaml", "json"), yamlSafeObject, fc.string({ maxLength: 30 })])(
|
|
418
378
|
"should handle different languages",
|
|
419
|
-
(
|
|
379
|
+
(lang, data, content) => {
|
|
420
380
|
let frontMatterContent: string;
|
|
421
|
-
if (
|
|
381
|
+
if (lang === "json") {
|
|
422
382
|
frontMatterContent = JSON.stringify(data);
|
|
423
383
|
} else {
|
|
424
384
|
frontMatterContent = Object.entries(data)
|
|
@@ -426,20 +386,20 @@ if (import.meta.vitest) {
|
|
|
426
386
|
.join("\n");
|
|
427
387
|
}
|
|
428
388
|
|
|
429
|
-
const input = `---${
|
|
430
|
-
|
|
389
|
+
const input = `---${lang}\n${frontMatterContent}\n---\n${content}`;
|
|
390
|
+
clearCache();
|
|
431
391
|
const result = matter(input);
|
|
432
392
|
|
|
433
|
-
expect(result.language).toBe(
|
|
393
|
+
expect(result.language).toBe(lang);
|
|
434
394
|
expect(result.data).toEqual(data);
|
|
435
395
|
},
|
|
436
396
|
);
|
|
437
397
|
|
|
438
|
-
|
|
398
|
+
fcTest.prop([fc.constantFrom("---", "~~~", "***", "+++")])(
|
|
439
399
|
"should handle custom delimiters",
|
|
440
400
|
(delimiter) => {
|
|
441
401
|
const input = `${delimiter}\ntitle: Test\n${delimiter}\ncontent`;
|
|
442
|
-
|
|
402
|
+
clearCache();
|
|
443
403
|
const result = matter(input, { delimiters: delimiter });
|
|
444
404
|
|
|
445
405
|
expect(result.data).toEqual({ title: "Test" });
|
|
@@ -447,7 +407,7 @@ if (import.meta.vitest) {
|
|
|
447
407
|
},
|
|
448
408
|
);
|
|
449
409
|
|
|
450
|
-
|
|
410
|
+
fcTest.prop([
|
|
451
411
|
fc.string({ minLength: 1, maxLength: 20 }),
|
|
452
412
|
fc.string({ minLength: 1, maxLength: 20 }),
|
|
453
413
|
])("should extract excerpt with custom separator", (excerptText, contentText) => {
|
|
@@ -456,22 +416,22 @@ if (import.meta.vitest) {
|
|
|
456
416
|
const safeContent = contentText.replace(separator, "");
|
|
457
417
|
const input = `---\ntitle: Test\n---\n${safeExcerpt}\n${separator}\n${safeContent}`;
|
|
458
418
|
|
|
459
|
-
|
|
419
|
+
clearCache();
|
|
460
420
|
const result = matter(input, { excerpt: true, excerpt_separator: separator });
|
|
461
421
|
|
|
462
422
|
expect(result.excerpt).toBe(`${safeExcerpt}\n`);
|
|
463
423
|
});
|
|
464
424
|
|
|
465
|
-
|
|
425
|
+
fcTest.prop([fc.string({ minLength: 0, maxLength: 50 })])(
|
|
466
426
|
"should handle CRLF and LF consistently",
|
|
467
427
|
(content) => {
|
|
468
428
|
const yamlData = "title: Test";
|
|
469
429
|
const inputLF = `---\n${yamlData}\n---\n${content}`;
|
|
470
430
|
const inputCRLF = `---\r\n${yamlData}\r\n---\r\n${content}`;
|
|
471
431
|
|
|
472
|
-
|
|
432
|
+
clearCache();
|
|
473
433
|
const resultLF = matter(inputLF);
|
|
474
|
-
|
|
434
|
+
clearCache();
|
|
475
435
|
const resultCRLF = matter(inputCRLF);
|
|
476
436
|
|
|
477
437
|
expect(resultLF.data).toEqual(resultCRLF.data);
|
package/src/stringify.ts
CHANGED
|
@@ -79,7 +79,7 @@ if (import.meta.vitest) {
|
|
|
79
79
|
content: "hello world",
|
|
80
80
|
data: { title: "Test" },
|
|
81
81
|
excerpt: "",
|
|
82
|
-
orig:
|
|
82
|
+
orig: new Uint8Array(),
|
|
83
83
|
language: "yaml",
|
|
84
84
|
matter: "",
|
|
85
85
|
isEmpty: false,
|
|
@@ -104,7 +104,7 @@ if (import.meta.vitest) {
|
|
|
104
104
|
content: "no newline",
|
|
105
105
|
data: { key: "value" },
|
|
106
106
|
excerpt: "",
|
|
107
|
-
orig:
|
|
107
|
+
orig: new Uint8Array(),
|
|
108
108
|
language: "yaml",
|
|
109
109
|
matter: "",
|
|
110
110
|
isEmpty: false,
|
|
@@ -119,7 +119,7 @@ if (import.meta.vitest) {
|
|
|
119
119
|
content: "content only",
|
|
120
120
|
data: {},
|
|
121
121
|
excerpt: "",
|
|
122
|
-
orig:
|
|
122
|
+
orig: new Uint8Array(),
|
|
123
123
|
language: "yaml",
|
|
124
124
|
matter: "",
|
|
125
125
|
isEmpty: false,
|
|
@@ -134,7 +134,7 @@ if (import.meta.vitest) {
|
|
|
134
134
|
content: "main content",
|
|
135
135
|
data: { title: "Test" },
|
|
136
136
|
excerpt: "This is excerpt",
|
|
137
|
-
orig:
|
|
137
|
+
orig: new Uint8Array(),
|
|
138
138
|
language: "yaml",
|
|
139
139
|
matter: "",
|
|
140
140
|
isEmpty: false,
|
|
@@ -155,7 +155,7 @@ if (import.meta.vitest) {
|
|
|
155
155
|
content: "test content",
|
|
156
156
|
data,
|
|
157
157
|
excerpt: "",
|
|
158
|
-
orig:
|
|
158
|
+
orig: new Uint8Array(),
|
|
159
159
|
language: "yaml",
|
|
160
160
|
matter: "",
|
|
161
161
|
isEmpty: false,
|
|
@@ -172,14 +172,14 @@ if (import.meta.vitest) {
|
|
|
172
172
|
content,
|
|
173
173
|
data: { key: "value" },
|
|
174
174
|
excerpt: "",
|
|
175
|
-
orig:
|
|
175
|
+
orig: new Uint8Array(),
|
|
176
176
|
language: "yaml",
|
|
177
177
|
matter: "",
|
|
178
178
|
isEmpty: false,
|
|
179
179
|
stringify: () => "",
|
|
180
180
|
};
|
|
181
181
|
const result = stringify(file);
|
|
182
|
-
expect(
|
|
182
|
+
expect(result).toBeTypeOf("string");
|
|
183
183
|
expect(result.endsWith("\n")).toBe(true);
|
|
184
184
|
},
|
|
185
185
|
);
|
package/src/to-file.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { GrayMatterFile, GrayMatterInput, GrayMatterOptions } from "./types.ts";
|
|
2
|
-
import { getStringProp, isObject,
|
|
2
|
+
import { getStringProp, isObject, toUint8Array, toString } from "./utils.ts";
|
|
3
3
|
import { stringify } from "./stringify.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Internal input shape after normalization
|
|
7
7
|
*/
|
|
8
8
|
interface NormalizedInput {
|
|
9
|
-
content: string |
|
|
9
|
+
content: string | Uint8Array;
|
|
10
10
|
data?: unknown;
|
|
11
11
|
language?: string;
|
|
12
12
|
matter?: string;
|
|
@@ -24,7 +24,7 @@ function normalizeInput(input: GrayMatterInput): NormalizedInput {
|
|
|
24
24
|
matter: getStringProp(input, "matter"),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
// string or
|
|
27
|
+
// string or Uint8Array
|
|
28
28
|
return { content: input };
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -38,7 +38,7 @@ export function toFile(input: GrayMatterInput): GrayMatterFile {
|
|
|
38
38
|
const content = toString(normalized.content ?? "");
|
|
39
39
|
const language = normalized.language ?? "";
|
|
40
40
|
const matter = normalized.matter ?? "";
|
|
41
|
-
const orig =
|
|
41
|
+
const orig = toUint8Array(normalized.content ?? "");
|
|
42
42
|
|
|
43
43
|
const file: GrayMatterFile = {
|
|
44
44
|
data,
|
|
@@ -65,7 +65,6 @@ export function toFile(input: GrayMatterInput): GrayMatterFile {
|
|
|
65
65
|
|
|
66
66
|
if (import.meta.vitest) {
|
|
67
67
|
const { fc, test } = await import("@fast-check/vitest");
|
|
68
|
-
const { Buffer } = await import("node:buffer");
|
|
69
68
|
|
|
70
69
|
describe("toFile", () => {
|
|
71
70
|
it("should convert string to file object", () => {
|
|
@@ -78,8 +77,8 @@ if (import.meta.vitest) {
|
|
|
78
77
|
expect(result.matter).toBe("");
|
|
79
78
|
});
|
|
80
79
|
|
|
81
|
-
it("should convert
|
|
82
|
-
const result = toFile(
|
|
80
|
+
it("should convert Uint8Array to file object", () => {
|
|
81
|
+
const result = toFile(new TextEncoder().encode("buffer content"));
|
|
83
82
|
expect(result.content).toBe("buffer content");
|
|
84
83
|
expect(result.data).toEqual({});
|
|
85
84
|
});
|
|
@@ -90,15 +89,15 @@ if (import.meta.vitest) {
|
|
|
90
89
|
expect(result.data).toEqual({ key: "value" });
|
|
91
90
|
});
|
|
92
91
|
|
|
93
|
-
it("should preserve orig as
|
|
92
|
+
it("should preserve orig as Uint8Array", () => {
|
|
94
93
|
const result = toFile("test");
|
|
95
|
-
expect(
|
|
96
|
-
expect(result.orig
|
|
94
|
+
expect(result.orig).toBeInstanceOf(Uint8Array);
|
|
95
|
+
expect(new TextDecoder().decode(result.orig)).toBe("test");
|
|
97
96
|
});
|
|
98
97
|
|
|
99
98
|
it("should set stringify as a function", () => {
|
|
100
99
|
const result = toFile("content");
|
|
101
|
-
expect(
|
|
100
|
+
expect(result.stringify).toBeTypeOf("function");
|
|
102
101
|
});
|
|
103
102
|
|
|
104
103
|
it("should initialize data to empty object if not provided", () => {
|
|
@@ -126,25 +125,24 @@ if (import.meta.vitest) {
|
|
|
126
125
|
"should always return valid file object for any string",
|
|
127
126
|
(input) => {
|
|
128
127
|
const result = toFile(input);
|
|
129
|
-
expect(
|
|
130
|
-
expect(
|
|
128
|
+
expect(result.content).toBeTypeOf("string");
|
|
129
|
+
expect(result.data).toBeTypeOf("object");
|
|
131
130
|
expect(result.data).not.toBeNull();
|
|
132
|
-
expect(
|
|
133
|
-
expect(
|
|
134
|
-
expect(
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
137
|
-
expect(
|
|
131
|
+
expect(result.isEmpty).toBeTypeOf("boolean");
|
|
132
|
+
expect(result.excerpt).toBeTypeOf("string");
|
|
133
|
+
expect(result.language).toBeTypeOf("string");
|
|
134
|
+
expect(result.matter).toBeTypeOf("string");
|
|
135
|
+
expect(result.orig).toBeInstanceOf(Uint8Array);
|
|
136
|
+
expect(result.stringify).toBeTypeOf("function");
|
|
138
137
|
},
|
|
139
138
|
);
|
|
140
139
|
|
|
141
140
|
test.prop([fc.uint8Array({ minLength: 0, maxLength: 200 })])(
|
|
142
|
-
"should handle any
|
|
141
|
+
"should handle any Uint8Array input",
|
|
143
142
|
(arr) => {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
expect(
|
|
147
|
-
expect(Buffer.isBuffer(result.orig)).toBe(true);
|
|
143
|
+
const result = toFile(arr);
|
|
144
|
+
expect(result.content).toBeTypeOf("string");
|
|
145
|
+
expect(result.orig).toBeInstanceOf(Uint8Array);
|
|
148
146
|
},
|
|
149
147
|
);
|
|
150
148
|
|
|
@@ -159,7 +157,7 @@ if (import.meta.vitest) {
|
|
|
159
157
|
const input = data !== undefined ? { content, data } : { content };
|
|
160
158
|
const result = toFile(input);
|
|
161
159
|
expect(result.content).toBe(content);
|
|
162
|
-
expect(
|
|
160
|
+
expect(result.data).toBeTypeOf("object");
|
|
163
161
|
if (data !== undefined) {
|
|
164
162
|
expect(result.data).toEqual(data);
|
|
165
163
|
}
|
package/src/types.ts
CHANGED
|
@@ -45,8 +45,8 @@ export interface GrayMatterFile {
|
|
|
45
45
|
content: string;
|
|
46
46
|
/** The extracted excerpt (if enabled) */
|
|
47
47
|
excerpt: string;
|
|
48
|
-
/** The original input as a
|
|
49
|
-
orig:
|
|
48
|
+
/** The original input as a Uint8Array */
|
|
49
|
+
orig: Uint8Array;
|
|
50
50
|
/** The detected/specified language */
|
|
51
51
|
language: string;
|
|
52
52
|
/** The raw front matter string (without delimiters) */
|
|
@@ -55,8 +55,6 @@ export interface GrayMatterFile {
|
|
|
55
55
|
isEmpty: boolean;
|
|
56
56
|
/** The original content if isEmpty is true */
|
|
57
57
|
empty?: string;
|
|
58
|
-
/** File path (set by matter.read) */
|
|
59
|
-
path?: string;
|
|
60
58
|
/** Stringify the file back to a string */
|
|
61
59
|
stringify: (data?: Record<string, unknown>, options?: GrayMatterOptions) => string;
|
|
62
60
|
}
|
|
@@ -64,21 +62,7 @@ export interface GrayMatterFile {
|
|
|
64
62
|
/**
|
|
65
63
|
* Input that can be passed to gray-matter
|
|
66
64
|
*/
|
|
67
|
-
export type GrayMatterInput =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
}
|
|
65
|
+
export type GrayMatterInput =
|
|
66
|
+
| string
|
|
67
|
+
| Uint8Array
|
|
68
|
+
| { content: string; data?: Record<string, unknown> };
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
const textEncoder = new TextEncoder();
|
|
2
|
+
const textDecoder = new TextDecoder();
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Strip BOM (Byte Order Mark) from a string
|
|
@@ -8,33 +9,35 @@ function stripBom(str: string): string {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* Returns true if `val` is a
|
|
12
|
+
* Returns true if `val` is a Uint8Array
|
|
12
13
|
*/
|
|
13
|
-
function
|
|
14
|
-
return
|
|
14
|
+
function isUint8Array(val: unknown): val is Uint8Array {
|
|
15
|
+
return val instanceof Uint8Array;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Returns true if `val` is a plain object (not a
|
|
19
|
+
* Returns true if `val` is a plain object (not a Uint8Array or other special object)
|
|
19
20
|
*/
|
|
20
21
|
export function isObject(val: unknown): val is Record<string, unknown> {
|
|
21
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
typeof val === "object" && val !== null && !Array.isArray(val) && !(val instanceof Uint8Array)
|
|
24
|
+
);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
|
-
* Cast `input` to a
|
|
28
|
+
* Cast `input` to a Uint8Array
|
|
26
29
|
*/
|
|
27
|
-
export function
|
|
28
|
-
return typeof input === "string" ?
|
|
30
|
+
export function toUint8Array(input: string | Uint8Array): Uint8Array {
|
|
31
|
+
return typeof input === "string" ? textEncoder.encode(input) : input;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* Cast `input` to a string, stripping BOM
|
|
33
36
|
*/
|
|
34
|
-
export function toString(input: string |
|
|
35
|
-
if (
|
|
37
|
+
export function toString(input: string | Uint8Array): string {
|
|
38
|
+
if (isUint8Array(input)) return stripBom(textDecoder.decode(input));
|
|
36
39
|
if (typeof input !== "string") {
|
|
37
|
-
throw new TypeError("expected input to be a string or
|
|
40
|
+
throw new TypeError("expected input to be a string or Uint8Array");
|
|
38
41
|
}
|
|
39
42
|
return stripBom(input);
|
|
40
43
|
}
|
|
@@ -111,13 +114,13 @@ if (import.meta.vitest) {
|
|
|
111
114
|
});
|
|
112
115
|
});
|
|
113
116
|
|
|
114
|
-
describe("
|
|
115
|
-
it("should return true for
|
|
116
|
-
expect(
|
|
117
|
+
describe("isUint8Array", () => {
|
|
118
|
+
it("should return true for Uint8Array", () => {
|
|
119
|
+
expect(isUint8Array(new Uint8Array([1, 2, 3]))).toBe(true);
|
|
117
120
|
});
|
|
118
121
|
|
|
119
122
|
it("should return false for string", () => {
|
|
120
|
-
expect(
|
|
123
|
+
expect(isUint8Array("test")).toBe(false);
|
|
121
124
|
});
|
|
122
125
|
});
|
|
123
126
|
|
|
@@ -135,8 +138,8 @@ if (import.meta.vitest) {
|
|
|
135
138
|
expect(isObject(null)).toBe(false);
|
|
136
139
|
});
|
|
137
140
|
|
|
138
|
-
it("should return false for
|
|
139
|
-
expect(isObject(
|
|
141
|
+
it("should return false for Uint8Array", () => {
|
|
142
|
+
expect(isObject(new Uint8Array([1, 2, 3]))).toBe(false);
|
|
140
143
|
});
|
|
141
144
|
});
|
|
142
145
|
|