diff --git a/front_matter/_create_extractor.ts b/front_matter/_create_extractor.ts deleted file mode 100644 index 5ae505d12..000000000 --- a/front_matter/_create_extractor.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { EXTRACT_REGEXP_MAP, RECOGNIZE_REGEXP_MAP } from "./_formats.ts"; -import type { Extractor, Format } from "./_types.ts"; -import type { Extract } from "./types.ts"; - -/** 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 {@link https://yaml.org | YAML}, {@link https://toml.io | TOML} and - * {@link https://www.json.org/ | JSON}. - * - * @param str String to recognize. - * @param formats A list of formats to recognize. Defaults to all supported formats. - */ -function recognize(str: string, formats: Format[]): Format { - const [firstLine] = str.split(/(\r?\n)/) as [string]; - - for (const format of formats) { - if (RECOGNIZE_REGEXP_MAP.get(format)?.test(firstLine)) { - return format; - } - } - - throw new TypeError(`Unsupported front matter format.`); -} - -/** - * Factory that creates a function that extracts front matter from a string with - * the given parsers. Supports {@link https://yaml.org | YAML}, - * {@link https://toml.io | TOML} and {@link https://www.json.org/ | JSON}. - * - * For simple use cases where you know which format to parse in advance, use the - * pre-built extractors: - * - * - {@linkcode https://jsr.io/@std/front-matter/doc/yaml/~/extract | extractYaml} - * - {@linkcode https://jsr.io/@std/front-matter/doc/toml/~/extract | extractToml} - * - {@linkcode https://jsr.io/@std/front-matter/doc/json/~/extract | extractJson} - * - * @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. - */ -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 (!parser) throw new TypeError(`Unsupported front matter format`); - const regexp = EXTRACT_REGEXP_MAP.get(format); - if (!regexp) throw new TypeError(`Unsupported front matter format`); - - return _extract(str, regexp, parser); - }; -} diff --git a/front_matter/_create_extractor_test.ts b/front_matter/_create_extractor_test.ts deleted file mode 100644 index ce19f81ee..000000000 --- a/front_matter/_create_extractor_test.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -import { assertThrows } from "@std/assert"; -import { parse as parseYaml } from "@std/yaml/parse"; -import { parse as parseToml } from "@std/toml/parse"; -import { - resolveTestDataPath, - runExtractJsonTests, - runExtractTomlTests, - runExtractTypeErrorTests, - runExtractYamlTests1, - runExtractYamlTests2, -} from "./_test_utils.ts"; -import { createExtractor, type Parser } from "./_create_extractor.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, -}); -const extractAny = createExtractor({ - "yaml": parseYaml as Parser, - "json": JSON.parse as Parser, - "toml": parseToml as Parser, -}); - -// YAML // - -Deno.test("createExtractor() extracts yaml type error on invalid input", () => { - runExtractTypeErrorTests("yaml", extractYaml); -}); - -Deno.test("createExtractor() parses yaml delineate by `---`", async () => { - await runExtractYamlTests1(extractYaml); -}); - -Deno.test("createExtractor() parses yaml delineate by `---yaml`", async () => { - await runExtractYamlTests2(extractYaml); -}); - -Deno.test({ - name: - "createExtractor() handles text between horizontal rules should not be recognized", - async fn() { - const str = await Deno.readTextFile( - resolveTestDataPath("./horizontal_rules.md"), - ); - - assertThrows( - () => { - extractAny(str); - }, - TypeError, - "Unsupported front matter format", - ); - }, -}); - -// JSON // - -Deno.test("createExtractor() extracts json type error on invalid input", () => { - runExtractTypeErrorTests("json", extractJson); -}); - -Deno.test("createExtractor() parses json delineate by ---json", async () => { - await runExtractJsonTests(extractJson); -}); - -// TOML // - -Deno.test("createExtractor() extracts toml type error on invalid input", () => { - runExtractTypeErrorTests("toml", extractToml); -}); - -Deno.test("createExtractor() parses toml delineate by ---toml", async () => { - await runExtractTomlTests(extractToml); -}); - -// MULTIPLE FORMATS // - -Deno.test("createExtractor() parses yaml or json input", async () => { - await runExtractYamlTests1(extractYamlOrJson); - await runExtractYamlTests2(extractYamlOrJson); - await runExtractJsonTests(extractYamlOrJson); -}); - -Deno.test("createExtractor() parses any input", async () => { - await runExtractYamlTests1(extractAny); - await runExtractYamlTests2(extractAny); - await runExtractJsonTests(extractAny); - await runExtractTomlTests(extractAny); -}); diff --git a/front_matter/_formats.ts b/front_matter/_formats.ts index ef38b136f..389bf50cb 100644 --- a/front_matter/_formats.ts +++ b/front_matter/_formats.ts @@ -27,21 +27,21 @@ function createRegExps(delimiters: Delimiter[]): [RegExp, RegExp] { ]; } -const [RECOGNIZE_YAML_REGEXP, EXTRACT_YAML_REGEXP] = createRegExps( +export const [RECOGNIZE_YAML_REGEXP, EXTRACT_YAML_REGEXP] = createRegExps( [ ["---yaml", "---"], "= yaml =", "---", ], ); -const [RECOGNIZE_TOML_REGEXP, EXTRACT_TOML_REGEXP] = createRegExps( +export const [RECOGNIZE_TOML_REGEXP, EXTRACT_TOML_REGEXP] = createRegExps( [ ["---toml", "---"], "\\+\\+\\+", "= toml =", ], ); -const [RECOGNIZE_JSON_REGEXP, EXTRACT_JSON_REGEXP] = createRegExps( +export const [RECOGNIZE_JSON_REGEXP, EXTRACT_JSON_REGEXP] = createRegExps( [ ["---json", "---"], "= json =", diff --git a/front_matter/_shared.ts b/front_matter/_shared.ts new file mode 100644 index 000000000..67b524b65 --- /dev/null +++ b/front_matter/_shared.ts @@ -0,0 +1,44 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { RECOGNIZE_REGEXP_MAP } from "./_formats.ts"; +import type { Format } from "./_types.ts"; +import type { Extract } from "./types.ts"; + +/** Parser function type */ +export type Parser = >(str: string) => T; + +export function extractAndParse( + input: string, + extractRegExp: RegExp, + parse: Parser, +): Extract { + const match = extractRegExp.exec(input); + 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 = input.replace(match[0], ""); + return { frontMatter, body, attrs }; +} + +/** + * Recognizes the format of the front matter in a string. + * Supports {@link https://yaml.org | YAML}, {@link https://toml.io | TOML} and + * {@link https://www.json.org/ | JSON}. + * + * @param str String to recognize. + * @param formats A list of formats to recognize. Defaults to all supported formats. + */ +export function recognize( + str: string, + formats: Format[], +): Format { + const [firstLine] = str.split(/(\r?\n)/) as [string]; + + for (const format of formats) { + if (RECOGNIZE_REGEXP_MAP.get(format)?.test(firstLine)) return format; + } + + throw new TypeError(`Unsupported front matter format.`); +} diff --git a/front_matter/any.ts b/front_matter/any.ts index 376b44679..e3bc93f76 100644 --- a/front_matter/any.ts +++ b/front_matter/any.ts @@ -1,17 +1,24 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { createExtractor, type Parser } from "./_create_extractor.ts"; +import { extractAndParse, type Parser, recognize } from "./_shared.ts"; import { parse as parseYaml } from "@std/yaml/parse"; import { parse as parseToml } from "@std/toml/parse"; import type { Extract } from "./types.ts"; +import type { Format } from "./test.ts"; +import { EXTRACT_REGEXP_MAP } from "./_formats.ts"; export type { Extract }; -const _extractor = createExtractor({ - yaml: parseYaml as Parser, - toml: parseToml as Parser, - json: JSON.parse as Parser, -}); +function getParserForFormat(format: Format): Parser { + switch (format) { + case "yaml": + return parseYaml as Parser; + case "toml": + return parseToml as Parser; + case "json": + return JSON.parse; + } +} /** * Extracts and parses {@link https://yaml.org | YAML}, {@link https://toml.io | @@ -40,5 +47,9 @@ const _extractor = createExtractor({ * @returns The extracted front matter and body content. */ export function extract(text: string): Extract { - return _extractor(text); + const formats = [...EXTRACT_REGEXP_MAP.keys()] as Format[]; + const format = recognize(text, formats); + const regexp = EXTRACT_REGEXP_MAP.get(format) as RegExp; + const parser = getParserForFormat(format); + return extractAndParse(text, regexp, parser); } diff --git a/front_matter/json.ts b/front_matter/json.ts index 0d133cc08..8a6a767f6 100644 --- a/front_matter/json.ts +++ b/front_matter/json.ts @@ -1,14 +1,11 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { createExtractor, type Parser } from "./_create_extractor.ts"; +import { extractAndParse } from "./_shared.ts"; +import { EXTRACT_JSON_REGEXP } from "./_formats.ts"; import type { Extract } from "./types.ts"; export type { Extract }; -const _extractor = createExtractor({ - ["json"]: JSON.parse as Parser, -}); - /** * Extracts and parses {@link https://www.json.org/ | JSON } from the metadata * of front matter content. @@ -36,5 +33,5 @@ const _extractor = createExtractor({ * @returns The extracted JSON front matter and body content. */ export function extract(text: string): Extract { - return _extractor(text); + return extractAndParse(text, EXTRACT_JSON_REGEXP, JSON.parse); } diff --git a/front_matter/toml.ts b/front_matter/toml.ts index 10175edd9..bd6c00c81 100644 --- a/front_matter/toml.ts +++ b/front_matter/toml.ts @@ -1,15 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { createExtractor, type Parser } from "./_create_extractor.ts"; +import { extractAndParse, type Parser } from "./_shared.ts"; import { parse } from "@std/toml/parse"; import type { Extract } from "./types.ts"; +import { EXTRACT_TOML_REGEXP } from "./_formats.ts"; export type { Extract }; -const _extractor = createExtractor({ - ["toml"]: parse as Parser, -}); - /** * Extracts and parses {@link https://toml.io | TOML} from the metadata of * front matter content. @@ -37,5 +34,5 @@ const _extractor = createExtractor({ * @returns The extracted TOML front matter and body content. */ export function extract(text: string): Extract { - return _extractor(text); + return extractAndParse(text, EXTRACT_TOML_REGEXP, parse as Parser); } diff --git a/front_matter/yaml.ts b/front_matter/yaml.ts index 3acd048df..05d3366ae 100644 --- a/front_matter/yaml.ts +++ b/front_matter/yaml.ts @@ -1,15 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { createExtractor, type Parser } from "./_create_extractor.ts"; +import { extractAndParse, type Parser } from "./_shared.ts"; import { parse } from "@std/yaml/parse"; import type { Extract } from "./types.ts"; +import { EXTRACT_YAML_REGEXP } from "./_formats.ts"; export type { Extract }; -const _extractor = createExtractor({ - ["yaml"]: parse as Parser, -}); - /** * Extracts and parses {@link https://yaml.org | YAML} from the metadata of * front matter content. @@ -37,5 +34,5 @@ const _extractor = createExtractor({ * @returns The extracted YAML front matter and body content. */ export function extract(text: string): Extract { - return _extractor(text); + return extractAndParse(text, EXTRACT_YAML_REGEXP, parse as Parser); }