// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { MAP_FORMAT_TO_EXTRACTOR_RX, MAP_FORMAT_TO_RECOGNIZER_RX, } from "./_formats.ts"; type Format = "yaml" | "toml" | "json" | "unknown"; /** Return type for {@linkcode Extractor}. */ export type Extract = { frontMatter: string; body: string; attrs: T; }; /** Function return type for {@linkcode createExtractor}. */ export type Extractor = >( str: string, ) => Extract; /** Parser function type used alongside {@linkcode createExtractor}. */ export type Parser = >(str: string) => T; function _extract( str: string, rx: RegExp, parse: Parser, ): Extract { const match = rx.exec(str); if (!match || match.index !== 0) { throw new TypeError("Unexpected end of input"); } const frontMatter = match.at(-1)?.replace(/^\s+|\s+$/g, "") || ""; const attrs = parse(frontMatter) as T; const body = str.replace(match[0], ""); return { frontMatter, body, attrs }; } /** * Recognizes the format of the front matter in a string. Supports YAML, TOML and JSON. * * @param str String to recognize. * @param formats A list of formats to recognize. Defaults to all supported formats. * * ```ts * import { recognize } from "https://deno.land/std@$STD_VERSION/front_matter/mod.ts"; * import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts"; * * assertEquals(recognize("---\ntitle: Three dashes marks the spot\n---\n"), "yaml"); * assertEquals(recognize("---toml\ntitle = 'Three dashes followed by format marks the spot'\n---\n"), "toml"); * assertEquals(recognize("---json\n{\"title\": \"Three dashes followed by format marks the spot\"}\n---\n"), "json"); * assertEquals(recognize("---xml\nThree dashes marks the spot\n---\n"), "unknown"); * * assertEquals(recognize("---json\nThree dashes marks the spot\n---\n", ["yaml"]), "unknown"); */ function recognize(str: string, formats?: Format[]): Format { if (!formats) { formats = Object.keys(MAP_FORMAT_TO_RECOGNIZER_RX) as Format[]; } const [firstLine] = str.split(/(\r?\n)/) as [string]; for (const format of formats) { if (format === "unknown") { continue; } if (MAP_FORMAT_TO_RECOGNIZER_RX[format].test(firstLine)) { return format; } } return "unknown"; } /** * Factory that creates a function that extracts front matter from a string with the given parsers. * Supports YAML, TOML and JSON. * * @param formats A descriptor containing Format-parser pairs to use for each format. * @returns A function that extracts front matter from a string with the given parsers. * * ```ts * import { createExtractor, Parser } from "https://deno.land/std@$STD_VERSION/front_matter/mod.ts"; * import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts"; * import { parse as parseYAML } from "https://deno.land/std@$STD_VERSION/yaml/parse.ts"; * import { parse as parseTOML } from "https://deno.land/std@$STD_VERSION/toml/parse.ts"; * const extractYAML = createExtractor({ yaml: parseYAML as Parser }); * const extractTOML = createExtractor({ toml: parseTOML as Parser }); * const extractJSON = createExtractor({ json: JSON.parse as Parser }); * const extractYAMLOrJSON = createExtractor({ * yaml: parseYAML as Parser, * json: JSON.parse as Parser, * }); * * let { attrs, body, frontMatter } = extractYAML<{ title: string }>("---\ntitle: Three dashes marks the spot\n---\nferret"); * assertEquals(attrs.title, "Three dashes marks the spot"); * assertEquals(body, "ferret"); * assertEquals(frontMatter, "title: Three dashes marks the spot"); * * ({ attrs, body, frontMatter } = extractTOML<{ title: string }>("---toml\ntitle = 'Three dashes followed by format marks the spot'\n---\n")); * assertEquals(attrs.title, "Three dashes followed by format marks the spot"); * assertEquals(body, ""); * assertEquals(frontMatter, "title = 'Three dashes followed by format marks the spot'"); * * ({ attrs, body, frontMatter } = extractJSON<{ title: string }>("---json\n{\"title\": \"Three dashes followed by format marks the spot\"}\n---\ngoat")); * assertEquals(attrs.title, "Three dashes followed by format marks the spot"); * assertEquals(body, "goat"); * assertEquals(frontMatter, "{\"title\": \"Three dashes followed by format marks the spot\"}"); * * ({ attrs, body, frontMatter } = extractYAMLOrJSON<{ title: string }>("---\ntitle: Three dashes marks the spot\n---\nferret")); * assertEquals(attrs.title, "Three dashes marks the spot"); * assertEquals(body, "ferret"); * assertEquals(frontMatter, "title: Three dashes marks the spot"); * * ({ attrs, body, frontMatter } = extractYAMLOrJSON<{ title: string }>("---json\n{\"title\": \"Three dashes followed by format marks the spot\"}\n---\ngoat")); * assertEquals(attrs.title, "Three dashes followed by format marks the spot"); * assertEquals(body, "goat"); * assertEquals(frontMatter, "{\"title\": \"Three dashes followed by format marks the spot\"}"); * ``` */ export function createExtractor( formats: Partial>, ): Extractor { const formatKeys = Object.keys(formats) as Format[]; return function extract(str: string): Extract { const format = recognize(str, formatKeys); const parser = formats[format]; if (format === "unknown" || !parser) { throw new TypeError(`Unsupported front matter format`); } return _extract(str, MAP_FORMAT_TO_EXTRACTOR_RX[format], parser); }; }