refactor(front-matter): remove createExtractor() (#5378)

Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
This commit is contained in:
Tim Reichen 2024-07-11 08:43:36 +02:00 committed by GitHub
parent faa633bd12
commit a32c05fe26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 74 additions and 197 deletions

View File

@ -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 = <T = Record<string, unknown>>(str: string) => T;
function _extract<T>(
str: string,
rx: RegExp,
parse: Parser,
): Extract<T> {
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<Record<Format, Parser>>,
): Extractor {
const formatKeys = Object.keys(formats) as Format[];
return function extract<T>(str: string): Extract<T> {
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);
};
}

View File

@ -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);
});

View File

@ -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 =",

44
front_matter/_shared.ts Normal file
View File

@ -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 = <T = Record<string, unknown>>(str: string) => T;
export function extractAndParse<T>(
input: string,
extractRegExp: RegExp,
parse: Parser,
): Extract<T> {
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.`);
}

View File

@ -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<T>(text: string): Extract<T> {
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);
}

View File

@ -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<T>(text: string): Extract<T> {
return _extractor(text);
return extractAndParse(text, EXTRACT_JSON_REGEXP, JSON.parse);
}

View File

@ -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<T>(text: string): Extract<T> {
return _extractor(text);
return extractAndParse(text, EXTRACT_TOML_REGEXP, parse as Parser);
}

View File

@ -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<T>(text: string): Extract<T> {
return _extractor(text);
return extractAndParse(text, EXTRACT_YAML_REGEXP, parse as Parser);
}