gray-matter-es 0.1.0 → 0.1.1
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 +67 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ pnpm add gray-matter-es
|
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
23
|
import matter from "gray-matter-es";
|
|
24
|
+
// or
|
|
25
|
+
import { matter } from "gray-matter-es";
|
|
24
26
|
|
|
25
27
|
// Parse front matter
|
|
26
28
|
const file = matter("---\ntitle: Hello\n---\nThis is content");
|
|
@@ -124,6 +126,71 @@ Detect the language specified after the opening delimiter.
|
|
|
124
126
|
- Removed `section-matter` support
|
|
125
127
|
- TypeScript-first with strict types
|
|
126
128
|
|
|
129
|
+
## Migration from gray-matter
|
|
130
|
+
|
|
131
|
+
### Import Changes
|
|
132
|
+
|
|
133
|
+
```diff
|
|
134
|
+
- const matter = require('gray-matter');
|
|
135
|
+
+ import matter from 'gray-matter-es';
|
|
136
|
+
+ // or
|
|
137
|
+
+ import { matter } from 'gray-matter-es';
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Removed Features
|
|
141
|
+
|
|
142
|
+
#### JavaScript Front Matter Engine
|
|
143
|
+
|
|
144
|
+
The JavaScript engine has been removed for security reasons (it used `eval`). If you were using JavaScript front matter:
|
|
145
|
+
|
|
146
|
+
```markdown
|
|
147
|
+
---js
|
|
148
|
+
{
|
|
149
|
+
title: "Hello",
|
|
150
|
+
date: new Date()
|
|
151
|
+
}
|
|
152
|
+
---
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
You'll need to either:
|
|
156
|
+
|
|
157
|
+
1. Convert to YAML or JSON front matter
|
|
158
|
+
2. Register a custom engine with your own parser
|
|
159
|
+
|
|
160
|
+
#### Deprecated Options
|
|
161
|
+
|
|
162
|
+
The following deprecated options have been removed:
|
|
163
|
+
|
|
164
|
+
| Removed Option | Replacement |
|
|
165
|
+
| -------------- | ------------ |
|
|
166
|
+
| `lang` | `language` |
|
|
167
|
+
| `delims` | `delimiters` |
|
|
168
|
+
| `parsers` | `engines` |
|
|
169
|
+
|
|
170
|
+
```diff
|
|
171
|
+
- matter(str, { lang: 'json', delims: '~~~' });
|
|
172
|
+
+ matter(str, { language: 'json', delimiters: '~~~' });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Section Matter
|
|
176
|
+
|
|
177
|
+
If you were using `section-matter` functionality, you'll need to handle it separately.
|
|
178
|
+
|
|
179
|
+
### YAML Parser Differences
|
|
180
|
+
|
|
181
|
+
This library uses `@std/yaml` instead of `js-yaml`. In most cases, this is a drop-in replacement, but there may be edge cases with non-standard YAML.
|
|
182
|
+
|
|
183
|
+
### CommonJS Users
|
|
184
|
+
|
|
185
|
+
If you're using CommonJS, you'll need to either:
|
|
186
|
+
|
|
187
|
+
1. Migrate to ESM
|
|
188
|
+
2. Use dynamic import:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
const matter = await import("gray-matter-es").then((m) => m.default);
|
|
192
|
+
```
|
|
193
|
+
|
|
127
194
|
## License
|
|
128
195
|
|
|
129
196
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -8,5 +8,5 @@ import { BuiltinLanguage } from "./engines.mjs";
|
|
|
8
8
|
*/
|
|
9
9
|
declare const matter: MatterFunction;
|
|
10
10
|
//#endregion
|
|
11
|
-
export { type BuiltinLanguage, type Engine, type GrayMatterFile, type GrayMatterInput, type GrayMatterOptions, type MatterFunction, type ResolvedOptions, matter as default };
|
|
11
|
+
export { type BuiltinLanguage, type Engine, type GrayMatterFile, type GrayMatterInput, type GrayMatterOptions, type MatterFunction, type ResolvedOptions, matter as default, matter };
|
|
12
12
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { defaults } from \"./defaults.ts\";\nimport { toBuiltinLanguage } from \"./engines.ts\";\nimport { excerpt } from \"./excerpt.ts\";\nimport { parse } from \"./parse.ts\";\nimport { stringify } from \"./stringify.ts\";\nimport { toFile } from \"./to-file.ts\";\nimport type {\n GrayMatterFile,\n GrayMatterInput,\n GrayMatterOptions,\n MatterFunction,\n} from \"./types.ts\";\n\nexport type {\n Engine,\n GrayMatterFile,\n GrayMatterInput,\n GrayMatterOptions,\n MatterFunction,\n ResolvedOptions,\n} from \"./types.ts\";\n\nexport type { BuiltinLanguage } from \"./engines.ts\";\n\n/**\n * Cache for parsed results\n */\nconst cache = new Map<string, GrayMatterFile>();\n\n/**\n * Takes a string or object with `content` property, extracts\n * and parses front-matter from the string, then returns an object\n * with `data`, `content` and other useful properties.\n *\n * @example\n * ```ts\n * import matter from 'gray-matter-es';\n * console.log(matter('---\\ntitle: Home\\n---\\nOther stuff'));\n * //=> { data: { title: 'Home'}, content: 'Other stuff' }\n * ```\n */\nfunction matterImpl(input: GrayMatterInput, options?: GrayMatterOptions): GrayMatterFile {\n if (input === \"\") {\n return { ...toFile(input), isEmpty: true };\n }\n\n let file = toFile(input);\n const cached = cache.get(file.content);\n\n if (!options) {\n if (cached) {\n file = { ...cached };\n file.orig = cached.orig;\n return file;\n }\n\n // only cache if there are no options passed\n cache.set(file.content, file);\n }\n\n return parseMatter(file, options);\n}\n\n/**\n * Parse front matter from file\n */\nfunction parseMatter(file: GrayMatterFile, options?: GrayMatterOptions): GrayMatterFile {\n const opts = defaults(options);\n const open = opts.delimiters[0];\n const close = \"\\n\" + opts.delimiters[1];\n let str = file.content;\n\n if (opts.language) {\n file.language = opts.language;\n }\n\n // get the length of the opening delimiter\n const openLen = open.length;\n if (!str.startsWith(open)) {\n excerpt(file, opts);\n return file;\n }\n\n // if the next character after the opening delimiter is\n // a character from the delimiter, then it's not a front-\n // matter delimiter\n if (str.at(openLen) === open.at(-1)) {\n return file;\n }\n\n // strip the opening delimiter\n str = str.slice(openLen);\n const len = str.length;\n\n // use the language defined after first delimiter, if it exists\n const lang = matterLanguage(str, opts);\n if (lang.name) {\n file.language = lang.name;\n str = str.slice(lang.raw.length);\n }\n\n // get the index of the closing delimiter\n let closeIndex = str.indexOf(close);\n if (closeIndex === -1) {\n closeIndex = len;\n }\n\n // get the raw front-matter block\n file.matter = str.slice(0, closeIndex);\n\n const block = file.matter.replace(/^\\s*#[^\\n]+/gm, \"\").trim();\n if (block === \"\") {\n file.isEmpty = true;\n file.empty = file.content;\n file.data = {};\n } else {\n // create file.data by parsing the raw file.matter block\n file.data = parse(toBuiltinLanguage(file.language), file.matter);\n }\n\n // update file.content\n if (closeIndex === len) {\n file.content = \"\";\n } else {\n file.content = str.slice(closeIndex + close.length);\n if (file.content.at(0) === \"\\r\") {\n file.content = file.content.slice(1);\n }\n if (file.content.at(0) === \"\\n\") {\n file.content = file.content.slice(1);\n }\n }\n\n excerpt(file, opts);\n return file;\n}\n\n/**\n * Detect the language to use, if one is defined after the\n * first front-matter delimiter.\n */\nfunction matterLanguage(str: string, options?: GrayMatterOptions): { raw: string; name: string } {\n const opts = defaults(options);\n const open = opts.delimiters[0];\n\n if (matterTest(str, opts)) {\n str = str.slice(open.length);\n }\n\n const language = str.slice(0, str.search(/\\r?\\n/));\n return {\n raw: language,\n name: language ? language.trim() : \"\",\n };\n}\n\n/**\n * Returns true if the given string has front matter.\n */\nfunction matterTest(str: string, options?: GrayMatterOptions): boolean {\n return str.startsWith(defaults(options).delimiters[0]);\n}\n\n/**\n * The matter function with all static methods\n */\nconst matter: MatterFunction = Object.assign(matterImpl, {\n /**\n * Stringify an object to YAML or the specified language, and\n * append it to the given string.\n */\n stringify: (\n file: GrayMatterFile | string,\n data?: Record<string, unknown>,\n options?: GrayMatterOptions,\n ): string => {\n if (typeof file === \"string\") file = matterImpl(file, options);\n return stringify(file, data, options);\n },\n\n /**\n * Synchronously read a file from the file system and parse front matter.\n */\n read: (filepath: string, options?: GrayMatterOptions): GrayMatterFile => {\n const str = readFileSync(filepath, \"utf8\");\n const file = matterImpl(str, options);\n file.path = filepath;\n return file;\n },\n\n /**\n * Returns true if the given string has front matter.\n */\n test: matterTest,\n\n /**\n * Detect the language to use, if one is defined after the\n * first front-matter delimiter.\n */\n language: matterLanguage,\n\n /**\n * Clear the cache\n */\n clearCache: (): void => {\n cache.clear();\n },\n\n /**\n * Expose cache (read-only access)\n */\n cache,\n});\n\nexport default matter;\n\nif (import.meta.vitest) {\n const { fc, test } = await import(\"@fast-check/vitest\");\n const { Buffer } = await import(\"node:buffer\");\n\n describe(\"matter\", () => {\n beforeEach(() => {\n matter.clearCache();\n });\n\n it(\"should extract YAML front matter\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\");\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.content).toBe(\"\");\n });\n\n it(\"should extract YAML front matter with content\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\\nfoo\");\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.content).toBe(\"foo\");\n });\n\n it(\"should return empty object for empty string\", () => {\n const actual = matter(\"\");\n expect(actual.data).toEqual({});\n expect(actual.content).toBe(\"\");\n });\n\n it(\"should return content when no front matter\", () => {\n const actual = matter(\"foo bar\");\n expect(actual.data).toEqual({});\n expect(actual.content).toBe(\"foo bar\");\n });\n\n it(\"should handle CRLF line endings\", () => {\n const actual = matter(\"---\\r\\nabc: xyz\\r\\n---\\r\\ncontent\");\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.content).toBe(\"content\");\n });\n\n it(\"should detect language after delimiter\", () => {\n const actual = matter('---json\\n{\"abc\": \"xyz\"}\\n---\\ncontent');\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.language).toBe(\"json\");\n });\n\n it(\"should handle custom delimiters\", () => {\n const actual = matter(\"~~~\\nabc: xyz\\n~~~\\ncontent\", {\n delimiters: \"~~~\",\n });\n expect(actual.data).toEqual({ abc: \"xyz\" });\n });\n\n it(\"should extract excerpt when enabled\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\\nexcerpt\\n---\\ncontent\", {\n excerpt: true,\n });\n expect(actual.excerpt).toBe(\"excerpt\\n\");\n });\n\n it(\"should use custom excerpt separator\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\\nexcerpt\\n<!-- more -->\\ncontent\", {\n excerpt: true,\n excerpt_separator: \"<!-- more -->\",\n });\n expect(actual.excerpt).toBe(\"excerpt\\n\");\n });\n\n it(\"should cache results when no options\", () => {\n const input = \"---\\nabc: xyz\\n---\";\n const first = matter(input);\n const second = matter(input);\n expect(first.data).toEqual(second.data);\n });\n\n it(\"should not cache when options provided\", () => {\n const input = \"---\\nabc: xyz\\n---\";\n matter(input);\n const second = matter(input, { language: \"yaml\" });\n expect(second.data).toEqual({ abc: \"xyz\" });\n });\n });\n\n describe(\"matter.stringify\", () => {\n it(\"should stringify data to YAML front matter\", () => {\n const result = matter.stringify(\"content\", { title: \"Hello\" });\n expect(result).toContain(\"---\");\n expect(result).toContain(\"title: Hello\");\n expect(result).toContain(\"content\");\n });\n\n it(\"should stringify file object\", () => {\n const file = matter(\"---\\ntitle: Test\\n---\\ncontent\");\n const result = matter.stringify(file, { title: \"Updated\" });\n expect(result).toContain(\"title: Updated\");\n });\n });\n\n describe(\"matter.test\", () => {\n it(\"should return true for string with front matter\", () => {\n expect(matter.test(\"---\\nabc: xyz\\n---\")).toBe(true);\n });\n\n it(\"should return false for string without front matter\", () => {\n expect(matter.test(\"foo bar\")).toBe(false);\n });\n });\n\n describe(\"matter.language\", () => {\n it(\"should detect language after delimiter\", () => {\n const result = matter.language(\"---json\\n{}\\n---\");\n expect(result.name).toBe(\"json\");\n });\n\n it(\"should return empty for no language\", () => {\n const result = matter.language(\"---\\nabc: xyz\\n---\");\n expect(result.name).toBe(\"\");\n });\n });\n\n describe(\"property-based tests\", () => {\n beforeEach(() => {\n matter.clearCache();\n });\n\n /** Arbitrary for YAML-safe keys */\n const yamlKey = fc\n .string({ minLength: 1, maxLength: 20 })\n .filter((s) => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s));\n\n /** Arbitrary for YAML-safe values */\n const yamlSafeValue = fc.oneof(\n fc.string({ maxLength: 50 }),\n fc.integer({ min: -10000, max: 10000 }),\n fc.boolean(),\n );\n\n /** Arbitrary for simple YAML-compatible objects */\n const yamlSafeObject = fc.dictionary(yamlKey, yamlSafeValue, { minKeys: 1, maxKeys: 5 });\n\n test.prop([fc.string({ minLength: 1, maxLength: 100 })])(\n \"Buffer and string input should produce equivalent results\",\n (content) => {\n matter.clearCache();\n const fromString = matter(content);\n matter.clearCache();\n const fromBuffer = matter(Buffer.from(content));\n\n expect(fromString.content).toBe(fromBuffer.content);\n expect(fromString.data).toEqual(fromBuffer.data);\n expect(fromString.excerpt).toBe(fromBuffer.excerpt);\n },\n );\n\n test.prop([yamlSafeObject, fc.string({ minLength: 0, maxLength: 50 })])(\n \"parse then stringify should preserve data\",\n (data, content) => {\n const frontMatter = Object.entries(data)\n .map(([k, v]) => `${k}: ${typeof v === \"string\" ? JSON.stringify(v) : v}`)\n .join(\"\\n\");\n const input = `---\\n${frontMatter}\\n---\\n${content}`;\n\n matter.clearCache();\n const parsed = matter(input);\n const stringified = matter.stringify(parsed);\n matter.clearCache();\n const reparsed = matter(stringified);\n\n expect(reparsed.data).toEqual(parsed.data);\n },\n );\n\n test.prop([fc.string({ minLength: 0, maxLength: 100 })])(\n \"content without front matter should be preserved\",\n (content) => {\n const safeContent = content.replace(/^---/gm, \"___\");\n matter.clearCache();\n const result = matter(safeContent);\n\n expect(result.content).toBe(safeContent);\n expect(result.data).toEqual({});\n },\n );\n\n test.prop([\n yamlSafeObject,\n fc.string({ minLength: 0, maxLength: 50 }),\n fc.string({ minLength: 1, maxLength: 50 }),\n ])(\"matter.test should correctly detect front matter\", (data, content, noFrontMatter) => {\n const frontMatter = Object.entries(data)\n .map(([k, v]) => `${k}: ${typeof v === \"string\" ? JSON.stringify(v) : v}`)\n .join(\"\\n\");\n const withFrontMatter = `---\\n${frontMatter}\\n---\\n${content}`;\n const withoutFrontMatter = noFrontMatter.replace(/^---/gm, \"___\");\n\n expect(matter.test(withFrontMatter)).toBe(true);\n expect(matter.test(withoutFrontMatter)).toBe(withoutFrontMatter.startsWith(\"---\"));\n });\n\n test.prop([fc.constantFrom(\"yaml\", \"json\"), yamlSafeObject, fc.string({ maxLength: 30 })])(\n \"should handle different languages\",\n (language, data, content) => {\n let frontMatterContent: string;\n if (language === \"json\") {\n frontMatterContent = JSON.stringify(data);\n } else {\n frontMatterContent = Object.entries(data)\n .map(([k, v]) => `${k}: ${typeof v === \"string\" ? JSON.stringify(v) : v}`)\n .join(\"\\n\");\n }\n\n const input = `---${language}\\n${frontMatterContent}\\n---\\n${content}`;\n matter.clearCache();\n const result = matter(input);\n\n expect(result.language).toBe(language);\n expect(result.data).toEqual(data);\n },\n );\n\n test.prop([fc.constantFrom(\"---\", \"~~~\", \"***\", \"+++\")])(\n \"should handle custom delimiters\",\n (delimiter) => {\n const input = `${delimiter}\\ntitle: Test\\n${delimiter}\\ncontent`;\n matter.clearCache();\n const result = matter(input, { delimiters: delimiter });\n\n expect(result.data).toEqual({ title: \"Test\" });\n expect(result.content).toBe(\"content\");\n },\n );\n\n test.prop([\n fc.string({ minLength: 1, maxLength: 20 }),\n fc.string({ minLength: 1, maxLength: 20 }),\n ])(\"should extract excerpt with custom separator\", (excerptText, contentText) => {\n const separator = \"<!-- more -->\";\n const safeExcerpt = excerptText.replace(separator, \"\");\n const safeContent = contentText.replace(separator, \"\");\n const input = `---\\ntitle: Test\\n---\\n${safeExcerpt}\\n${separator}\\n${safeContent}`;\n\n matter.clearCache();\n const result = matter(input, { excerpt: true, excerpt_separator: separator });\n\n expect(result.excerpt).toBe(`${safeExcerpt}\\n`);\n });\n\n test.prop([fc.string({ minLength: 0, maxLength: 50 })])(\n \"should handle CRLF and LF consistently\",\n (content) => {\n const yamlData = \"title: Test\";\n const inputLF = `---\\n${yamlData}\\n---\\n${content}`;\n const inputCRLF = `---\\r\\n${yamlData}\\r\\n---\\r\\n${content}`;\n\n matter.clearCache();\n const resultLF = matter(inputLF);\n matter.clearCache();\n const resultCRLF = matter(inputCRLF);\n\n expect(resultLF.data).toEqual(resultCRLF.data);\n expect(resultLF.content).toBe(resultCRLF.content);\n },\n );\n });\n}\n"],"mappings":";;;;;;;;;;;;AA4BA,MAAM,wBAAQ,IAAI,KAA6B;;;;;;;;;;;;;AAc/C,SAAS,WAAW,OAAwB,SAA6C;AACvF,KAAI,UAAU,GACZ,QAAO;EAAE,GAAG,OAAO,MAAM;EAAE,SAAS;EAAM;CAG5C,IAAI,OAAO,OAAO,MAAM;CACxB,MAAM,SAAS,MAAM,IAAI,KAAK,QAAQ;AAEtC,KAAI,CAAC,SAAS;AACZ,MAAI,QAAQ;AACV,UAAO,EAAE,GAAG,QAAQ;AACpB,QAAK,OAAO,OAAO;AACnB,UAAO;;AAIT,QAAM,IAAI,KAAK,SAAS,KAAK;;AAG/B,QAAO,YAAY,MAAM,QAAQ;;;;;AAMnC,SAAS,YAAY,MAAsB,SAA6C;CACtF,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,OAAO,KAAK,WAAW;CAC7B,MAAM,QAAQ,OAAO,KAAK,WAAW;CACrC,IAAI,MAAM,KAAK;AAEf,KAAI,KAAK,SACP,MAAK,WAAW,KAAK;CAIvB,MAAM,UAAU,KAAK;AACrB,KAAI,CAAC,IAAI,WAAW,KAAK,EAAE;AACzB,UAAQ,MAAM,KAAK;AACnB,SAAO;;AAMT,KAAI,IAAI,GAAG,QAAQ,KAAK,KAAK,GAAG,GAAG,CACjC,QAAO;AAIT,OAAM,IAAI,MAAM,QAAQ;CACxB,MAAM,MAAM,IAAI;CAGhB,MAAM,OAAO,eAAe,KAAK,KAAK;AACtC,KAAI,KAAK,MAAM;AACb,OAAK,WAAW,KAAK;AACrB,QAAM,IAAI,MAAM,KAAK,IAAI,OAAO;;CAIlC,IAAI,aAAa,IAAI,QAAQ,MAAM;AACnC,KAAI,eAAe,GACjB,cAAa;AAIf,MAAK,SAAS,IAAI,MAAM,GAAG,WAAW;AAGtC,KADc,KAAK,OAAO,QAAQ,iBAAiB,GAAG,CAAC,MAAM,KAC/C,IAAI;AAChB,OAAK,UAAU;AACf,OAAK,QAAQ,KAAK;AAClB,OAAK,OAAO,EAAE;OAGd,MAAK,OAAO,MAAM,kBAAkB,KAAK,SAAS,EAAE,KAAK,OAAO;AAIlE,KAAI,eAAe,IACjB,MAAK,UAAU;MACV;AACL,OAAK,UAAU,IAAI,MAAM,aAAa,MAAM,OAAO;AACnD,MAAI,KAAK,QAAQ,GAAG,EAAE,KAAK,KACzB,MAAK,UAAU,KAAK,QAAQ,MAAM,EAAE;AAEtC,MAAI,KAAK,QAAQ,GAAG,EAAE,KAAK,KACzB,MAAK,UAAU,KAAK,QAAQ,MAAM,EAAE;;AAIxC,SAAQ,MAAM,KAAK;AACnB,QAAO;;;;;;AAOT,SAAS,eAAe,KAAa,SAA4D;CAC/F,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,OAAO,KAAK,WAAW;AAE7B,KAAI,WAAW,KAAK,KAAK,CACvB,OAAM,IAAI,MAAM,KAAK,OAAO;CAG9B,MAAM,WAAW,IAAI,MAAM,GAAG,IAAI,OAAO,QAAQ,CAAC;AAClD,QAAO;EACL,KAAK;EACL,MAAM,WAAW,SAAS,MAAM,GAAG;EACpC;;;;;AAMH,SAAS,WAAW,KAAa,SAAsC;AACrE,QAAO,IAAI,WAAW,SAAS,QAAQ,CAAC,WAAW,GAAG;;;;;AAMxD,MAAM,SAAyB,OAAO,OAAO,YAAY;CAKvD,YACE,MACA,MACA,YACW;AACX,MAAI,OAAO,SAAS,SAAU,QAAO,WAAW,MAAM,QAAQ;AAC9D,SAAO,UAAU,MAAM,MAAM,QAAQ;;CAMvC,OAAO,UAAkB,YAAgD;EAEvE,MAAM,OAAO,WADD,aAAa,UAAU,OAAO,EACb,QAAQ;AACrC,OAAK,OAAO;AACZ,SAAO;;CAMT,MAAM;CAMN,UAAU;CAKV,kBAAwB;AACtB,QAAM,OAAO;;CAMf;CACD,CAAC;AAEF,kBAAe"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { defaults } from \"./defaults.ts\";\nimport { toBuiltinLanguage } from \"./engines.ts\";\nimport { excerpt } from \"./excerpt.ts\";\nimport { parse } from \"./parse.ts\";\nimport { stringify } from \"./stringify.ts\";\nimport { toFile } from \"./to-file.ts\";\nimport type {\n GrayMatterFile,\n GrayMatterInput,\n GrayMatterOptions,\n MatterFunction,\n} from \"./types.ts\";\n\nexport type {\n Engine,\n GrayMatterFile,\n GrayMatterInput,\n GrayMatterOptions,\n MatterFunction,\n ResolvedOptions,\n} from \"./types.ts\";\n\nexport type { BuiltinLanguage } from \"./engines.ts\";\n\n/**\n * Cache for parsed results\n */\nconst cache = new Map<string, GrayMatterFile>();\n\n/**\n * Takes a string or object with `content` property, extracts\n * and parses front-matter from the string, then returns an object\n * with `data`, `content` and other useful properties.\n *\n * @example\n * ```ts\n * import matter from 'gray-matter-es';\n * console.log(matter('---\\ntitle: Home\\n---\\nOther stuff'));\n * //=> { data: { title: 'Home'}, content: 'Other stuff' }\n * ```\n */\nfunction matterImpl(input: GrayMatterInput, options?: GrayMatterOptions): GrayMatterFile {\n if (input === \"\") {\n return { ...toFile(input), isEmpty: true };\n }\n\n let file = toFile(input);\n const cached = cache.get(file.content);\n\n if (!options) {\n if (cached) {\n file = { ...cached };\n file.orig = cached.orig;\n return file;\n }\n\n // only cache if there are no options passed\n cache.set(file.content, file);\n }\n\n return parseMatter(file, options);\n}\n\n/**\n * Parse front matter from file\n */\nfunction parseMatter(file: GrayMatterFile, options?: GrayMatterOptions): GrayMatterFile {\n const opts = defaults(options);\n const open = opts.delimiters[0];\n const close = \"\\n\" + opts.delimiters[1];\n let str = file.content;\n\n if (opts.language) {\n file.language = opts.language;\n }\n\n // get the length of the opening delimiter\n const openLen = open.length;\n if (!str.startsWith(open)) {\n excerpt(file, opts);\n return file;\n }\n\n // if the next character after the opening delimiter is\n // a character from the delimiter, then it's not a front-\n // matter delimiter\n if (str.at(openLen) === open.at(-1)) {\n return file;\n }\n\n // strip the opening delimiter\n str = str.slice(openLen);\n const len = str.length;\n\n // use the language defined after first delimiter, if it exists\n const lang = matterLanguage(str, opts);\n if (lang.name) {\n file.language = lang.name;\n str = str.slice(lang.raw.length);\n }\n\n // get the index of the closing delimiter\n let closeIndex = str.indexOf(close);\n if (closeIndex === -1) {\n closeIndex = len;\n }\n\n // get the raw front-matter block\n file.matter = str.slice(0, closeIndex);\n\n const block = file.matter.replace(/^\\s*#[^\\n]+/gm, \"\").trim();\n if (block === \"\") {\n file.isEmpty = true;\n file.empty = file.content;\n file.data = {};\n } else {\n // create file.data by parsing the raw file.matter block\n file.data = parse(toBuiltinLanguage(file.language), file.matter);\n }\n\n // update file.content\n if (closeIndex === len) {\n file.content = \"\";\n } else {\n file.content = str.slice(closeIndex + close.length);\n if (file.content.at(0) === \"\\r\") {\n file.content = file.content.slice(1);\n }\n if (file.content.at(0) === \"\\n\") {\n file.content = file.content.slice(1);\n }\n }\n\n excerpt(file, opts);\n return file;\n}\n\n/**\n * Detect the language to use, if one is defined after the\n * first front-matter delimiter.\n */\nfunction matterLanguage(str: string, options?: GrayMatterOptions): { raw: string; name: string } {\n const opts = defaults(options);\n const open = opts.delimiters[0];\n\n if (matterTest(str, opts)) {\n str = str.slice(open.length);\n }\n\n const language = str.slice(0, str.search(/\\r?\\n/));\n return {\n raw: language,\n name: language ? language.trim() : \"\",\n };\n}\n\n/**\n * Returns true if the given string has front matter.\n */\nfunction matterTest(str: string, options?: GrayMatterOptions): boolean {\n return str.startsWith(defaults(options).delimiters[0]);\n}\n\n/**\n * The matter function with all static methods\n */\nconst matter: MatterFunction = Object.assign(matterImpl, {\n /**\n * Stringify an object to YAML or the specified language, and\n * append it to the given string.\n */\n stringify: (\n file: GrayMatterFile | string,\n data?: Record<string, unknown>,\n options?: GrayMatterOptions,\n ): string => {\n if (typeof file === \"string\") file = matterImpl(file, options);\n return stringify(file, data, options);\n },\n\n /**\n * Synchronously read a file from the file system and parse front matter.\n */\n read: (filepath: string, options?: GrayMatterOptions): GrayMatterFile => {\n const str = readFileSync(filepath, \"utf8\");\n const file = matterImpl(str, options);\n file.path = filepath;\n return file;\n },\n\n /**\n * Returns true if the given string has front matter.\n */\n test: matterTest,\n\n /**\n * Detect the language to use, if one is defined after the\n * first front-matter delimiter.\n */\n language: matterLanguage,\n\n /**\n * Clear the cache\n */\n clearCache: (): void => {\n cache.clear();\n },\n\n /**\n * Expose cache (read-only access)\n */\n cache,\n});\n\nexport { matter };\nexport default matter;\n\nif (import.meta.vitest) {\n const { fc, test } = await import(\"@fast-check/vitest\");\n const { Buffer } = await import(\"node:buffer\");\n\n describe(\"matter\", () => {\n beforeEach(() => {\n matter.clearCache();\n });\n\n it(\"should extract YAML front matter\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\");\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.content).toBe(\"\");\n });\n\n it(\"should extract YAML front matter with content\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\\nfoo\");\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.content).toBe(\"foo\");\n });\n\n it(\"should return empty object for empty string\", () => {\n const actual = matter(\"\");\n expect(actual.data).toEqual({});\n expect(actual.content).toBe(\"\");\n });\n\n it(\"should return content when no front matter\", () => {\n const actual = matter(\"foo bar\");\n expect(actual.data).toEqual({});\n expect(actual.content).toBe(\"foo bar\");\n });\n\n it(\"should handle CRLF line endings\", () => {\n const actual = matter(\"---\\r\\nabc: xyz\\r\\n---\\r\\ncontent\");\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.content).toBe(\"content\");\n });\n\n it(\"should detect language after delimiter\", () => {\n const actual = matter('---json\\n{\"abc\": \"xyz\"}\\n---\\ncontent');\n expect(actual.data).toEqual({ abc: \"xyz\" });\n expect(actual.language).toBe(\"json\");\n });\n\n it(\"should handle custom delimiters\", () => {\n const actual = matter(\"~~~\\nabc: xyz\\n~~~\\ncontent\", {\n delimiters: \"~~~\",\n });\n expect(actual.data).toEqual({ abc: \"xyz\" });\n });\n\n it(\"should extract excerpt when enabled\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\\nexcerpt\\n---\\ncontent\", {\n excerpt: true,\n });\n expect(actual.excerpt).toBe(\"excerpt\\n\");\n });\n\n it(\"should use custom excerpt separator\", () => {\n const actual = matter(\"---\\nabc: xyz\\n---\\nexcerpt\\n<!-- more -->\\ncontent\", {\n excerpt: true,\n excerpt_separator: \"<!-- more -->\",\n });\n expect(actual.excerpt).toBe(\"excerpt\\n\");\n });\n\n it(\"should cache results when no options\", () => {\n const input = \"---\\nabc: xyz\\n---\";\n const first = matter(input);\n const second = matter(input);\n expect(first.data).toEqual(second.data);\n });\n\n it(\"should not cache when options provided\", () => {\n const input = \"---\\nabc: xyz\\n---\";\n matter(input);\n const second = matter(input, { language: \"yaml\" });\n expect(second.data).toEqual({ abc: \"xyz\" });\n });\n });\n\n describe(\"matter.stringify\", () => {\n it(\"should stringify data to YAML front matter\", () => {\n const result = matter.stringify(\"content\", { title: \"Hello\" });\n expect(result).toContain(\"---\");\n expect(result).toContain(\"title: Hello\");\n expect(result).toContain(\"content\");\n });\n\n it(\"should stringify file object\", () => {\n const file = matter(\"---\\ntitle: Test\\n---\\ncontent\");\n const result = matter.stringify(file, { title: \"Updated\" });\n expect(result).toContain(\"title: Updated\");\n });\n });\n\n describe(\"matter.test\", () => {\n it(\"should return true for string with front matter\", () => {\n expect(matter.test(\"---\\nabc: xyz\\n---\")).toBe(true);\n });\n\n it(\"should return false for string without front matter\", () => {\n expect(matter.test(\"foo bar\")).toBe(false);\n });\n });\n\n describe(\"matter.language\", () => {\n it(\"should detect language after delimiter\", () => {\n const result = matter.language(\"---json\\n{}\\n---\");\n expect(result.name).toBe(\"json\");\n });\n\n it(\"should return empty for no language\", () => {\n const result = matter.language(\"---\\nabc: xyz\\n---\");\n expect(result.name).toBe(\"\");\n });\n });\n\n describe(\"property-based tests\", () => {\n beforeEach(() => {\n matter.clearCache();\n });\n\n /** Arbitrary for YAML-safe keys */\n const yamlKey = fc\n .string({ minLength: 1, maxLength: 20 })\n .filter((s) => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s));\n\n /** Arbitrary for YAML-safe values */\n const yamlSafeValue = fc.oneof(\n fc.string({ maxLength: 50 }),\n fc.integer({ min: -10000, max: 10000 }),\n fc.boolean(),\n );\n\n /** Arbitrary for simple YAML-compatible objects */\n const yamlSafeObject = fc.dictionary(yamlKey, yamlSafeValue, { minKeys: 1, maxKeys: 5 });\n\n test.prop([fc.string({ minLength: 1, maxLength: 100 })])(\n \"Buffer and string input should produce equivalent results\",\n (content) => {\n matter.clearCache();\n const fromString = matter(content);\n matter.clearCache();\n const fromBuffer = matter(Buffer.from(content));\n\n expect(fromString.content).toBe(fromBuffer.content);\n expect(fromString.data).toEqual(fromBuffer.data);\n expect(fromString.excerpt).toBe(fromBuffer.excerpt);\n },\n );\n\n test.prop([yamlSafeObject, fc.string({ minLength: 0, maxLength: 50 })])(\n \"parse then stringify should preserve data\",\n (data, content) => {\n const frontMatter = Object.entries(data)\n .map(([k, v]) => `${k}: ${typeof v === \"string\" ? JSON.stringify(v) : v}`)\n .join(\"\\n\");\n const input = `---\\n${frontMatter}\\n---\\n${content}`;\n\n matter.clearCache();\n const parsed = matter(input);\n const stringified = matter.stringify(parsed);\n matter.clearCache();\n const reparsed = matter(stringified);\n\n expect(reparsed.data).toEqual(parsed.data);\n },\n );\n\n test.prop([fc.string({ minLength: 0, maxLength: 100 })])(\n \"content without front matter should be preserved\",\n (content) => {\n const safeContent = content.replace(/^---/gm, \"___\");\n matter.clearCache();\n const result = matter(safeContent);\n\n expect(result.content).toBe(safeContent);\n expect(result.data).toEqual({});\n },\n );\n\n test.prop([\n yamlSafeObject,\n fc.string({ minLength: 0, maxLength: 50 }),\n fc.string({ minLength: 1, maxLength: 50 }),\n ])(\"matter.test should correctly detect front matter\", (data, content, noFrontMatter) => {\n const frontMatter = Object.entries(data)\n .map(([k, v]) => `${k}: ${typeof v === \"string\" ? JSON.stringify(v) : v}`)\n .join(\"\\n\");\n const withFrontMatter = `---\\n${frontMatter}\\n---\\n${content}`;\n const withoutFrontMatter = noFrontMatter.replace(/^---/gm, \"___\");\n\n expect(matter.test(withFrontMatter)).toBe(true);\n expect(matter.test(withoutFrontMatter)).toBe(withoutFrontMatter.startsWith(\"---\"));\n });\n\n test.prop([fc.constantFrom(\"yaml\", \"json\"), yamlSafeObject, fc.string({ maxLength: 30 })])(\n \"should handle different languages\",\n (language, data, content) => {\n let frontMatterContent: string;\n if (language === \"json\") {\n frontMatterContent = JSON.stringify(data);\n } else {\n frontMatterContent = Object.entries(data)\n .map(([k, v]) => `${k}: ${typeof v === \"string\" ? JSON.stringify(v) : v}`)\n .join(\"\\n\");\n }\n\n const input = `---${language}\\n${frontMatterContent}\\n---\\n${content}`;\n matter.clearCache();\n const result = matter(input);\n\n expect(result.language).toBe(language);\n expect(result.data).toEqual(data);\n },\n );\n\n test.prop([fc.constantFrom(\"---\", \"~~~\", \"***\", \"+++\")])(\n \"should handle custom delimiters\",\n (delimiter) => {\n const input = `${delimiter}\\ntitle: Test\\n${delimiter}\\ncontent`;\n matter.clearCache();\n const result = matter(input, { delimiters: delimiter });\n\n expect(result.data).toEqual({ title: \"Test\" });\n expect(result.content).toBe(\"content\");\n },\n );\n\n test.prop([\n fc.string({ minLength: 1, maxLength: 20 }),\n fc.string({ minLength: 1, maxLength: 20 }),\n ])(\"should extract excerpt with custom separator\", (excerptText, contentText) => {\n const separator = \"<!-- more -->\";\n const safeExcerpt = excerptText.replace(separator, \"\");\n const safeContent = contentText.replace(separator, \"\");\n const input = `---\\ntitle: Test\\n---\\n${safeExcerpt}\\n${separator}\\n${safeContent}`;\n\n matter.clearCache();\n const result = matter(input, { excerpt: true, excerpt_separator: separator });\n\n expect(result.excerpt).toBe(`${safeExcerpt}\\n`);\n });\n\n test.prop([fc.string({ minLength: 0, maxLength: 50 })])(\n \"should handle CRLF and LF consistently\",\n (content) => {\n const yamlData = \"title: Test\";\n const inputLF = `---\\n${yamlData}\\n---\\n${content}`;\n const inputCRLF = `---\\r\\n${yamlData}\\r\\n---\\r\\n${content}`;\n\n matter.clearCache();\n const resultLF = matter(inputLF);\n matter.clearCache();\n const resultCRLF = matter(inputCRLF);\n\n expect(resultLF.data).toEqual(resultCRLF.data);\n expect(resultLF.content).toBe(resultCRLF.content);\n },\n );\n });\n}\n"],"mappings":";;;;;;;;;;;;AA4BA,MAAM,wBAAQ,IAAI,KAA6B;;;;;;;;;;;;;AAc/C,SAAS,WAAW,OAAwB,SAA6C;AACvF,KAAI,UAAU,GACZ,QAAO;EAAE,GAAG,OAAO,MAAM;EAAE,SAAS;EAAM;CAG5C,IAAI,OAAO,OAAO,MAAM;CACxB,MAAM,SAAS,MAAM,IAAI,KAAK,QAAQ;AAEtC,KAAI,CAAC,SAAS;AACZ,MAAI,QAAQ;AACV,UAAO,EAAE,GAAG,QAAQ;AACpB,QAAK,OAAO,OAAO;AACnB,UAAO;;AAIT,QAAM,IAAI,KAAK,SAAS,KAAK;;AAG/B,QAAO,YAAY,MAAM,QAAQ;;;;;AAMnC,SAAS,YAAY,MAAsB,SAA6C;CACtF,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,OAAO,KAAK,WAAW;CAC7B,MAAM,QAAQ,OAAO,KAAK,WAAW;CACrC,IAAI,MAAM,KAAK;AAEf,KAAI,KAAK,SACP,MAAK,WAAW,KAAK;CAIvB,MAAM,UAAU,KAAK;AACrB,KAAI,CAAC,IAAI,WAAW,KAAK,EAAE;AACzB,UAAQ,MAAM,KAAK;AACnB,SAAO;;AAMT,KAAI,IAAI,GAAG,QAAQ,KAAK,KAAK,GAAG,GAAG,CACjC,QAAO;AAIT,OAAM,IAAI,MAAM,QAAQ;CACxB,MAAM,MAAM,IAAI;CAGhB,MAAM,OAAO,eAAe,KAAK,KAAK;AACtC,KAAI,KAAK,MAAM;AACb,OAAK,WAAW,KAAK;AACrB,QAAM,IAAI,MAAM,KAAK,IAAI,OAAO;;CAIlC,IAAI,aAAa,IAAI,QAAQ,MAAM;AACnC,KAAI,eAAe,GACjB,cAAa;AAIf,MAAK,SAAS,IAAI,MAAM,GAAG,WAAW;AAGtC,KADc,KAAK,OAAO,QAAQ,iBAAiB,GAAG,CAAC,MAAM,KAC/C,IAAI;AAChB,OAAK,UAAU;AACf,OAAK,QAAQ,KAAK;AAClB,OAAK,OAAO,EAAE;OAGd,MAAK,OAAO,MAAM,kBAAkB,KAAK,SAAS,EAAE,KAAK,OAAO;AAIlE,KAAI,eAAe,IACjB,MAAK,UAAU;MACV;AACL,OAAK,UAAU,IAAI,MAAM,aAAa,MAAM,OAAO;AACnD,MAAI,KAAK,QAAQ,GAAG,EAAE,KAAK,KACzB,MAAK,UAAU,KAAK,QAAQ,MAAM,EAAE;AAEtC,MAAI,KAAK,QAAQ,GAAG,EAAE,KAAK,KACzB,MAAK,UAAU,KAAK,QAAQ,MAAM,EAAE;;AAIxC,SAAQ,MAAM,KAAK;AACnB,QAAO;;;;;;AAOT,SAAS,eAAe,KAAa,SAA4D;CAC/F,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,OAAO,KAAK,WAAW;AAE7B,KAAI,WAAW,KAAK,KAAK,CACvB,OAAM,IAAI,MAAM,KAAK,OAAO;CAG9B,MAAM,WAAW,IAAI,MAAM,GAAG,IAAI,OAAO,QAAQ,CAAC;AAClD,QAAO;EACL,KAAK;EACL,MAAM,WAAW,SAAS,MAAM,GAAG;EACpC;;;;;AAMH,SAAS,WAAW,KAAa,SAAsC;AACrE,QAAO,IAAI,WAAW,SAAS,QAAQ,CAAC,WAAW,GAAG;;;;;AAMxD,MAAM,SAAyB,OAAO,OAAO,YAAY;CAKvD,YACE,MACA,MACA,YACW;AACX,MAAI,OAAO,SAAS,SAAU,QAAO,WAAW,MAAM,QAAQ;AAC9D,SAAO,UAAU,MAAM,MAAM,QAAQ;;CAMvC,OAAO,UAAkB,YAAgD;EAEvE,MAAM,OAAO,WADD,aAAa,UAAU,OAAO,EACb,QAAQ;AACrC,OAAK,OAAO;AACZ,SAAO;;CAMT,MAAM;CAMN,UAAU;CAKV,kBAAwB;AACtB,QAAM,OAAO;;CAMf;CACD,CAAC;AAGF,kBAAe"}
|
package/package.json
CHANGED