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 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
@@ -122,5 +122,5 @@ const matter = Object.assign(matterImpl, {
122
122
  var src_default = matter;
123
123
 
124
124
  //#endregion
125
- export { src_default as default };
125
+ export { src_default as default, matter };
126
126
  //# sourceMappingURL=index.mjs.map
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gray-matter-es",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "ES-only gray-matter",
5
5
  "keywords": [
6
6
  "esm",
package/src/index.ts CHANGED
@@ -213,6 +213,7 @@ const matter: MatterFunction = Object.assign(matterImpl, {
213
213
  cache,
214
214
  });
215
215
 
216
+ export { matter };
216
217
  export default matter;
217
218
 
218
219
  if (import.meta.vitest) {