micromark-extension-frontmatter
Advanced tools
Comparing version 1.0.1 to 1.1.0
@@ -0,3 +1,6 @@ | ||
export {frontmatter} from './lib/syntax.js' | ||
export {frontmatterHtml} from './lib/html.js' | ||
export {frontmatter} from './lib/syntax.js' | ||
export type Info = import('./matters.js').Info | ||
export type Matter = import('./matters.js').Matter | ||
export type Options = import('./matters.js').Options | ||
export type Preset = import('./matters.js').Preset |
/** | ||
* @typedef {import('./matters.js').Info} Info | ||
* @typedef {import('./matters.js').Matter} Matter | ||
* @typedef {import('./matters.js').Options} Options | ||
* @typedef {import('./matters.js').Preset} Preset | ||
*/ | ||
export {frontmatter} from './lib/syntax.js' | ||
export {frontmatterHtml} from './lib/html.js' | ||
export {frontmatter} from './lib/syntax.js' |
/** | ||
* Add support for turning frontmatter in markdown to HTML. | ||
* Create an extension for `micromark` to support frontmatter when serializing | ||
* to HTML. | ||
* | ||
* Function that can be called to get an HTML extension for micromark (passed | ||
* in `htmlExtensions`). | ||
* > π **Note**: this makes sure nothing is generated in the output HTML for | ||
* > frontmatter. | ||
* | ||
* This makes sure nothing is generated for frontmatter. | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support other things. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {HtmlExtension} | ||
* HTML extension for micromark (passed in `htmlExtensions`). | ||
* Extension for `micromark` that can be passed in `htmlExtensions`, to | ||
* support frontmatter when serializing to HTML. | ||
*/ | ||
export function frontmatterHtml( | ||
options?: import('../matters.js').Options | undefined | ||
options?: Options | null | undefined | ||
): HtmlExtension | ||
@@ -20,0 +17,0 @@ export type HtmlExtension = import('micromark-util-types').HtmlExtension |
@@ -11,19 +11,16 @@ /** | ||
/** | ||
* Add support for turning frontmatter in markdown to HTML. | ||
* Create an extension for `micromark` to support frontmatter when serializing | ||
* to HTML. | ||
* | ||
* Function that can be called to get an HTML extension for micromark (passed | ||
* in `htmlExtensions`). | ||
* > π **Note**: this makes sure nothing is generated in the output HTML for | ||
* > frontmatter. | ||
* | ||
* This makes sure nothing is generated for frontmatter. | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support other things. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {HtmlExtension} | ||
* HTML extension for micromark (passed in `htmlExtensions`). | ||
* Extension for `micromark` that can be passed in `htmlExtensions`, to | ||
* support frontmatter when serializing to HTML. | ||
*/ | ||
export function frontmatterHtml(options) { | ||
const settings = matters(options) | ||
const listOfMatters = matters(options) | ||
/** @type {HtmlExtension['enter']} */ | ||
@@ -35,4 +32,4 @@ const enter = {} | ||
while (++index < settings.length) { | ||
const type = settings[index].type | ||
while (++index < listOfMatters.length) { | ||
const type = listOfMatters[index].type | ||
enter[type] = start | ||
@@ -39,0 +36,0 @@ exit[type] = end |
/** | ||
* Add support for parsing frontmatter in markdown. | ||
* Create an extension for `micromark` to enable frontmatter syntax. | ||
* | ||
* Function that can be called to get a syntax extension for micromark (passed | ||
* in `extensions`). | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support TOML and more. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Extension} | ||
* Syntax extension for micromark (passed in `extensions`). | ||
* Extension for `micromark` that can be passed in `extensions`, to | ||
* enable frontmatter syntax. | ||
*/ | ||
export function frontmatter( | ||
options?: import('../matters.js').Options | undefined | ||
): Extension | ||
export function frontmatter(options?: Options | null | undefined): Extension | ||
export type Construct = import('micromark-util-types').Construct | ||
export type ConstructRecord = import('micromark-util-types').ConstructRecord | ||
export type Extension = import('micromark-util-types').Extension | ||
export type ConstructRecord = import('micromark-util-types').ConstructRecord | ||
export type Construct = import('micromark-util-types').Construct | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type State = import('micromark-util-types').State | ||
export type Info = import('../matters.js').Info | ||
export type Matter = import('../matters.js').Matter | ||
export type Options = import('../matters.js').Options | ||
export type Matter = import('../matters.js').Matter | ||
export type Info = import('../matters.js').Info |
/** | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord | ||
* @typedef {import('micromark-util-types').Extension} Extension | ||
* @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').State} State | ||
* | ||
* @typedef {import('../matters.js').Info} Info | ||
* @typedef {import('../matters.js').Matter} Matter | ||
* @typedef {import('../matters.js').Options} Options | ||
* @typedef {import('../matters.js').Matter} Matter | ||
* @typedef {import('../matters.js').Info} Info | ||
*/ | ||
@@ -19,17 +20,12 @@ | ||
/** | ||
* Add support for parsing frontmatter in markdown. | ||
* Create an extension for `micromark` to enable frontmatter syntax. | ||
* | ||
* Function that can be called to get a syntax extension for micromark (passed | ||
* in `extensions`). | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support TOML and more. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Extension} | ||
* Syntax extension for micromark (passed in `extensions`). | ||
* Extension for `micromark` that can be passed in `extensions`, to | ||
* enable frontmatter syntax. | ||
*/ | ||
export function frontmatter(options) { | ||
const settings = matters(options) | ||
const listOfMatters = matters(options) | ||
/** @type {ConstructRecord} */ | ||
@@ -39,10 +35,13 @@ const flow = {} | ||
while (++index < settings.length) { | ||
const matter = settings[index] | ||
while (++index < listOfMatters.length) { | ||
const matter = listOfMatters[index] | ||
const code = fence(matter, 'open').charCodeAt(0) | ||
if (code in flow) { | ||
// @ts-expect-error it clearly does exist. | ||
flow[code].push(parse(matter)) | ||
const construct = createConstruct(matter) | ||
const existing = flow[code] | ||
if (Array.isArray(existing)) { | ||
existing.push(construct) | ||
} else { | ||
flow[code] = [parse(matter)] | ||
// Never a single object, always an array. | ||
flow[code] = [construct] | ||
} | ||
@@ -58,11 +57,17 @@ } | ||
*/ | ||
function parse(matter) { | ||
const name = matter.type | ||
function createConstruct(matter) { | ||
const anywhere = matter.anywhere | ||
const valueType = name + 'Value' | ||
const fenceType = name + 'Fence' | ||
const frontmatterType = matter.type | ||
const fenceType = frontmatterType + 'Fence' | ||
const sequenceType = fenceType + 'Sequence' | ||
const fenceConstruct = {tokenize: tokenizeFence, partial: true} | ||
/** @type {string} */ | ||
const valueType = frontmatterType + 'Value' | ||
const closingFenceConstruct = {tokenize: tokenizeClosingFence, partial: true} | ||
/** | ||
* Fence to look for. | ||
* | ||
* @type {string} | ||
*/ | ||
let buffer | ||
let bufferIndex = 0 | ||
@@ -80,44 +85,175 @@ return {tokenize: tokenizeFrontmatter, concrete: true} | ||
/** @type {State} */ | ||
/** | ||
* Start of frontmatter. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function start(code) { | ||
const position = self.now() | ||
if (position.column !== 1 || (!anywhere && position.line !== 1)) { | ||
return nok(code) | ||
if ( | ||
// Indent not allowed. | ||
position.column === 1 && | ||
// Normally, only allowed in first line. | ||
(position.line === 1 || anywhere) | ||
) { | ||
buffer = fence(matter, 'open') | ||
bufferIndex = 0 | ||
if (code === buffer.charCodeAt(bufferIndex)) { | ||
effects.enter(frontmatterType) | ||
effects.enter(fenceType) | ||
effects.enter(sequenceType) | ||
return openSequence(code) | ||
} | ||
} | ||
effects.enter(name) | ||
buffer = fence(matter, 'open') | ||
return effects.attempt(fenceConstruct, afterOpeningFence, nok)(code) | ||
return nok(code) | ||
} | ||
/** @type {State} */ | ||
function afterOpeningFence(code) { | ||
buffer = fence(matter, 'close') | ||
return lineEnd(code) | ||
/** | ||
* In open sequence. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function openSequence(code) { | ||
if (bufferIndex === buffer.length) { | ||
effects.exit(sequenceType) | ||
if (markdownSpace(code)) { | ||
effects.enter(types.whitespace) | ||
return openSequenceWhitespace(code) | ||
} | ||
return openAfter(code) | ||
} | ||
if (code === buffer.charCodeAt(bufferIndex++)) { | ||
effects.consume(code) | ||
return openSequence | ||
} | ||
return nok(code) | ||
} | ||
/** @type {State} */ | ||
function lineStart(code) { | ||
/** | ||
* In whitespace after open sequence. | ||
* | ||
* ```markdown | ||
* > | ---β | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function openSequenceWhitespace(code) { | ||
if (markdownSpace(code)) { | ||
effects.consume(code) | ||
return openSequenceWhitespace | ||
} | ||
effects.exit(types.whitespace) | ||
return openAfter(code) | ||
} | ||
/** | ||
* After open sequence. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function openAfter(code) { | ||
if (markdownLineEnding(code)) { | ||
effects.exit(fenceType) | ||
effects.enter(types.lineEnding) | ||
effects.consume(code) | ||
effects.exit(types.lineEnding) | ||
// Get ready for closing fence. | ||
buffer = fence(matter, 'close') | ||
bufferIndex = 0 | ||
return effects.attempt(closingFenceConstruct, after, contentStart) | ||
} | ||
// EOF is not okay. | ||
return nok(code) | ||
} | ||
/** | ||
* Start of content chunk. | ||
* | ||
* ```markdown | ||
* | --- | ||
* > | title: "Venus" | ||
* ^ | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function contentStart(code) { | ||
if (code === codes.eof || markdownLineEnding(code)) { | ||
return lineEnd(code) | ||
return contentEnd(code) | ||
} | ||
effects.enter(valueType) | ||
return lineData(code) | ||
return contentInside(code) | ||
} | ||
/** @type {State} */ | ||
function lineData(code) { | ||
/** | ||
* In content chunk. | ||
* | ||
* ```markdown | ||
* | --- | ||
* > | title: "Venus" | ||
* ^ | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function contentInside(code) { | ||
if (code === codes.eof || markdownLineEnding(code)) { | ||
effects.exit(valueType) | ||
return lineEnd(code) | ||
return contentEnd(code) | ||
} | ||
effects.consume(code) | ||
return lineData | ||
return contentInside | ||
} | ||
/** @type {State} */ | ||
function lineEnd(code) { | ||
/** | ||
* End of content chunk. | ||
* | ||
* ```markdown | ||
* | --- | ||
* > | title: "Venus" | ||
* ^ | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function contentEnd(code) { | ||
// Require a closing fence. | ||
@@ -132,8 +268,20 @@ if (code === codes.eof) { | ||
effects.exit(types.lineEnding) | ||
return effects.attempt(fenceConstruct, after, lineStart) | ||
return effects.attempt(closingFenceConstruct, after, contentStart) | ||
} | ||
/** @type {State} */ | ||
/** | ||
* After frontmatter. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function after(code) { | ||
effects.exit(name) | ||
// `code` must be eol/eof. | ||
effects.exit(frontmatterType) | ||
return ok(code) | ||
@@ -144,13 +292,24 @@ } | ||
/** @type {Tokenizer} */ | ||
function tokenizeFence(effects, ok, nok) { | ||
function tokenizeClosingFence(effects, ok, nok) { | ||
let bufferIndex = 0 | ||
return start | ||
return closeStart | ||
/** @type {State} */ | ||
function start(code) { | ||
/** | ||
* Start of close sequence. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeStart(code) { | ||
if (code === buffer.charCodeAt(bufferIndex)) { | ||
effects.enter(fenceType) | ||
effects.enter(sequenceType) | ||
return insideSequence(code) | ||
return closeSequence(code) | ||
} | ||
@@ -161,4 +320,15 @@ | ||
/** @type {State} */ | ||
function insideSequence(code) { | ||
/** | ||
* In close sequence. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeSequence(code) { | ||
if (bufferIndex === buffer.length) { | ||
@@ -169,6 +339,6 @@ effects.exit(sequenceType) | ||
effects.enter(types.whitespace) | ||
return insideWhitespace(code) | ||
return closeSequenceWhitespace(code) | ||
} | ||
return fenceEnd(code) | ||
return closeAfter(code) | ||
} | ||
@@ -178,3 +348,3 @@ | ||
effects.consume(code) | ||
return insideSequence | ||
return closeSequence | ||
} | ||
@@ -185,15 +355,37 @@ | ||
/** @type {State} */ | ||
function insideWhitespace(code) { | ||
/** | ||
* In whitespace after close sequence. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* | title: "Venus" | ||
* | ---β | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeSequenceWhitespace(code) { | ||
if (markdownSpace(code)) { | ||
effects.consume(code) | ||
return insideWhitespace | ||
return closeSequenceWhitespace | ||
} | ||
effects.exit(types.whitespace) | ||
return fenceEnd(code) | ||
return closeAfter(code) | ||
} | ||
/** @type {State} */ | ||
function fenceEnd(code) { | ||
/** | ||
* After close sequence. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeAfter(code) { | ||
if (code === codes.eof || markdownLineEnding(code)) { | ||
@@ -211,3 +403,3 @@ effects.exit(fenceType) | ||
* @param {Matter} matter | ||
* @param {'open'|'close'} prop | ||
* @param {'open' | 'close'} prop | ||
* @returns {string} | ||
@@ -223,4 +415,4 @@ */ | ||
/** | ||
* @param {Info|string} schema | ||
* @param {'open'|'close'} prop | ||
* @param {Info | string} schema | ||
* @param {'open' | 'close'} prop | ||
* @returns {string} | ||
@@ -227,0 +419,0 @@ */ |
/** | ||
* @param {Options} [options='yaml'] | ||
* Simplify one or more options. | ||
* | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Array<Matter>} | ||
* List of matters. | ||
*/ | ||
export function matters(options?: Options | undefined): Array<Matter> | ||
export function matters(options?: Options | null | undefined): Array<Matter> | ||
/** | ||
* Either `'yaml'` or `'toml'`. | ||
* Known name of a frontmatter style. | ||
*/ | ||
export type Preset = 'yaml' | 'toml' | ||
export type Preset = 'toml' | 'yaml' | ||
/** | ||
* Sequence. | ||
* | ||
* Depending on how this structure is used, it reflects a marker or a fence. | ||
*/ | ||
export type Info = { | ||
/** | ||
* Opening. | ||
*/ | ||
open: string | ||
/** | ||
* Closing. | ||
*/ | ||
close: string | ||
} | ||
/** | ||
* Fields describing a kind of matter. | ||
*/ | ||
export type MatterProps = { | ||
/** | ||
* Type to tokenize as. | ||
* Node type to tokenize as. | ||
*/ | ||
type: string | ||
/** | ||
* If `true`, matter can be found anywhere in the document. | ||
* If `false` (default), only matter at the start of the document is | ||
* recognized. | ||
* Whether matter can be found anywhere in the document, normally, only matter | ||
* at the start of the document is recognized. | ||
* | ||
* > π **Note**: using this is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
*/ | ||
anywhere?: boolean | undefined | ||
anywhere?: boolean | null | undefined | ||
} | ||
@@ -31,13 +52,14 @@ /** | ||
/** | ||
* Character used to construct fences. | ||
* By providing an object with `open` and `close` different characters can be | ||
* used for opening and closing fences. | ||
* For example the character `'-'` will result in `'---'` being used as the | ||
* fence | ||
* Character repeated 3 times, used as complete fences. | ||
* | ||
* For example the character `'-'` will result in `'---'` being used as the | ||
* fence | ||
* Pass `open` and `close` to specify different characters for opening and | ||
* closing fences. | ||
*/ | ||
marker: string | Info | ||
marker: Info | string | ||
/** | ||
* If `marker` is set, `fence` must not be set. | ||
*/ | ||
fence?: undefined | ||
fence?: never | ||
} | ||
@@ -49,21 +71,29 @@ /** | ||
/** | ||
* String used as the complete fence. | ||
* By providing an object with `open` and `close` different values can be used | ||
* for opening and closing fences. | ||
* This can be used too if fences contain different characters or lengths | ||
* other than 3. | ||
* Complete fences. | ||
* | ||
* This can be used when fences contain different characters or lengths | ||
* other than 3. | ||
* Pass `open` and `close` to interface to specify different characters for opening and | ||
* closing fences. | ||
*/ | ||
fence: string | Info | ||
fence: Info | string | ||
/** | ||
* If `fence` is set, `marker` must not be set. | ||
*/ | ||
marker?: undefined | ||
marker?: never | ||
} | ||
/** | ||
* Matter object describing frontmatter. | ||
* Fields describing a kind of matter. | ||
* | ||
* > π **Note**: using `anywhere` is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
* | ||
* > π **Note**: `marker` and `fence` are mutually exclusive. | ||
* > If `marker` is set, `fence` must not be set, and vice versa. | ||
*/ | ||
export type Matter = (MatterProps & FenceProps) | (MatterProps & MarkerProps) | ||
/** | ||
* Matter object or preset, or many. | ||
* Configuration. | ||
*/ | ||
export type Options = Preset | Matter | Array<Preset | Matter> | ||
export type Options = Matter | Preset | Array<Matter | Preset> |
/** | ||
* @typedef {'yaml'|'toml'} Preset | ||
* Either `'yaml'` or `'toml'`. | ||
* @typedef {'toml' | 'yaml'} Preset | ||
* Known name of a frontmatter style. | ||
* | ||
* @typedef Info | ||
* Sequence. | ||
* | ||
* Depending on how this structure is used, it reflects a marker or a fence. | ||
* @property {string} open | ||
* Opening. | ||
* @property {string} close | ||
* Closing. | ||
* | ||
* @typedef MatterProps | ||
* Fields describing a kind of matter. | ||
* @property {string} type | ||
* Type to tokenize as. | ||
* @property {boolean} [anywhere=false] | ||
* If `true`, matter can be found anywhere in the document. | ||
* If `false` (default), only matter at the start of the document is | ||
* recognized. | ||
* Node type to tokenize as. | ||
* @property {boolean | null | undefined} [anywhere=false] | ||
* Whether matter can be found anywhere in the document, normally, only matter | ||
* at the start of the document is recognized. | ||
* | ||
* > π **Note**: using this is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
* | ||
* @typedef MarkerProps | ||
* Marker configuration. | ||
* @property {string|Info} marker | ||
* Character used to construct fences. | ||
* By providing an object with `open` and `close` different characters can be | ||
* used for opening and closing fences. | ||
* @property {Info | string} marker | ||
* Character repeated 3 times, used as complete fences. | ||
* | ||
* For example the character `'-'` will result in `'---'` being used as the | ||
* fence | ||
* Pass `open` and `close` to specify different characters for opening and | ||
* closing fences. | ||
* @property {never} [fence] | ||
@@ -30,16 +40,24 @@ * If `marker` is set, `fence` must not be set. | ||
* Fence configuration. | ||
* @property {string|Info} fence | ||
* String used as the complete fence. | ||
* By providing an object with `open` and `close` different values can be used | ||
* for opening and closing fences. | ||
* This can be used too if fences contain different characters or lengths | ||
* @property {Info | string} fence | ||
* Complete fences. | ||
* | ||
* This can be used when fences contain different characters or lengths | ||
* other than 3. | ||
* Pass `open` and `close` to interface to specify different characters for opening and | ||
* closing fences. | ||
* @property {never} [marker] | ||
* If `fence` is set, `marker` must not be set. | ||
* | ||
* @typedef {(MatterProps & FenceProps)|(MatterProps & MarkerProps)} Matter | ||
* Matter object describing frontmatter. | ||
* @typedef {(MatterProps & FenceProps) | (MatterProps & MarkerProps)} Matter | ||
* Fields describing a kind of matter. | ||
* | ||
* @typedef {Preset|Matter|Array<Preset|Matter>} Options | ||
* Matter object or preset, or many. | ||
* > π **Note**: using `anywhere` is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
* | ||
* > π **Note**: `marker` and `fence` are mutually exclusive. | ||
* > If `marker` is set, `fence` must not be set, and vice versa. | ||
* | ||
* @typedef {Matter | Preset | Array<Matter | Preset>} Options | ||
* Configuration. | ||
*/ | ||
@@ -53,25 +71,35 @@ | ||
/** | ||
* @param {Options} [options='yaml'] | ||
* Simplify one or more options. | ||
* | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Array<Matter>} | ||
* List of matters. | ||
*/ | ||
export function matters(options = 'yaml') { | ||
export function matters(options) { | ||
/** @type {Array<Matter>} */ | ||
const results = [] | ||
const result = [] | ||
let index = -1 | ||
// One preset or matter. | ||
if (!Array.isArray(options)) { | ||
options = [options] | ||
} | ||
/** @type {Array<Matter | Preset>} */ | ||
const presetsOrMatters = Array.isArray(options) | ||
? options | ||
: options | ||
? [options] | ||
: ['yaml'] | ||
while (++index < options.length) { | ||
results[index] = matter(options[index]) | ||
while (++index < presetsOrMatters.length) { | ||
result[index] = matter(presetsOrMatters[index]) | ||
} | ||
return results | ||
return result | ||
} | ||
/** | ||
* @param {Preset|Matter} option | ||
* Simplify an option. | ||
* | ||
* @param {Matter | Preset} option | ||
* Configuration. | ||
* @returns {Matter} | ||
* Matters. | ||
*/ | ||
@@ -78,0 +106,0 @@ function matter(option) { |
@@ -0,3 +1,6 @@ | ||
export {frontmatter} from './lib/syntax.js' | ||
export {frontmatterHtml} from './lib/html.js' | ||
export {frontmatter} from './lib/syntax.js' | ||
export type Info = import('./matters.js').Info | ||
export type Matter = import('./matters.js').Matter | ||
export type Options = import('./matters.js').Options | ||
export type Preset = import('./matters.js').Preset |
/** | ||
* @typedef {import('./matters.js').Info} Info | ||
* @typedef {import('./matters.js').Matter} Matter | ||
* @typedef {import('./matters.js').Options} Options | ||
* @typedef {import('./matters.js').Preset} Preset | ||
*/ | ||
export {frontmatter} from './lib/syntax.js' | ||
export {frontmatterHtml} from './lib/html.js' | ||
export {frontmatter} from './lib/syntax.js' |
/** | ||
* Add support for turning frontmatter in markdown to HTML. | ||
* Create an extension for `micromark` to support frontmatter when serializing | ||
* to HTML. | ||
* | ||
* Function that can be called to get an HTML extension for micromark (passed | ||
* in `htmlExtensions`). | ||
* > π **Note**: this makes sure nothing is generated in the output HTML for | ||
* > frontmatter. | ||
* | ||
* This makes sure nothing is generated for frontmatter. | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support other things. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {HtmlExtension} | ||
* HTML extension for micromark (passed in `htmlExtensions`). | ||
* Extension for `micromark` that can be passed in `htmlExtensions`, to | ||
* support frontmatter when serializing to HTML. | ||
*/ | ||
export function frontmatterHtml( | ||
options?: import('../matters.js').Options | undefined | ||
options?: Options | null | undefined | ||
): HtmlExtension | ||
@@ -20,0 +17,0 @@ export type HtmlExtension = import('micromark-util-types').HtmlExtension |
@@ -11,19 +11,16 @@ /** | ||
/** | ||
* Add support for turning frontmatter in markdown to HTML. | ||
* Create an extension for `micromark` to support frontmatter when serializing | ||
* to HTML. | ||
* | ||
* Function that can be called to get an HTML extension for micromark (passed | ||
* in `htmlExtensions`). | ||
* > π **Note**: this makes sure nothing is generated in the output HTML for | ||
* > frontmatter. | ||
* | ||
* This makes sure nothing is generated for frontmatter. | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support other things. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {HtmlExtension} | ||
* HTML extension for micromark (passed in `htmlExtensions`). | ||
* Extension for `micromark` that can be passed in `htmlExtensions`, to | ||
* support frontmatter when serializing to HTML. | ||
*/ | ||
export function frontmatterHtml(options) { | ||
const settings = matters(options) | ||
const listOfMatters = matters(options) | ||
/** @type {HtmlExtension['enter']} */ | ||
@@ -34,4 +31,4 @@ const enter = {} | ||
let index = -1 | ||
while (++index < settings.length) { | ||
const type = settings[index].type | ||
while (++index < listOfMatters.length) { | ||
const type = listOfMatters[index].type | ||
enter[type] = start | ||
@@ -38,0 +35,0 @@ exit[type] = end |
/** | ||
* Add support for parsing frontmatter in markdown. | ||
* Create an extension for `micromark` to enable frontmatter syntax. | ||
* | ||
* Function that can be called to get a syntax extension for micromark (passed | ||
* in `extensions`). | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support TOML and more. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Extension} | ||
* Syntax extension for micromark (passed in `extensions`). | ||
* Extension for `micromark` that can be passed in `extensions`, to | ||
* enable frontmatter syntax. | ||
*/ | ||
export function frontmatter( | ||
options?: import('../matters.js').Options | undefined | ||
): Extension | ||
export function frontmatter(options?: Options | null | undefined): Extension | ||
export type Construct = import('micromark-util-types').Construct | ||
export type ConstructRecord = import('micromark-util-types').ConstructRecord | ||
export type Extension = import('micromark-util-types').Extension | ||
export type ConstructRecord = import('micromark-util-types').ConstructRecord | ||
export type Construct = import('micromark-util-types').Construct | ||
export type State = import('micromark-util-types').State | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type Tokenizer = import('micromark-util-types').Tokenizer | ||
export type TokenizeContext = import('micromark-util-types').TokenizeContext | ||
export type State = import('micromark-util-types').State | ||
export type Info = import('../matters.js').Info | ||
export type Matter = import('../matters.js').Matter | ||
export type Options = import('../matters.js').Options | ||
export type Matter = import('../matters.js').Matter | ||
export type Info = import('../matters.js').Info |
/** | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord | ||
* @typedef {import('micromark-util-types').Extension} Extension | ||
* @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord | ||
* @typedef {import('micromark-util-types').Construct} Construct | ||
* @typedef {import('micromark-util-types').State} State | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer | ||
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext | ||
* @typedef {import('micromark-util-types').State} State | ||
* | ||
* @typedef {import('../matters.js').Info} Info | ||
* @typedef {import('../matters.js').Matter} Matter | ||
* @typedef {import('../matters.js').Options} Options | ||
* @typedef {import('../matters.js').Matter} Matter | ||
* @typedef {import('../matters.js').Info} Info | ||
*/ | ||
@@ -17,28 +18,25 @@ | ||
/** | ||
* Add support for parsing frontmatter in markdown. | ||
* Create an extension for `micromark` to enable frontmatter syntax. | ||
* | ||
* Function that can be called to get a syntax extension for micromark (passed | ||
* in `extensions`). | ||
* | ||
* Supports YAML by default. | ||
* Can be configured to support TOML and more. | ||
* | ||
* @param {Options} [options='yaml'] | ||
* Configuration (optional). | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Extension} | ||
* Syntax extension for micromark (passed in `extensions`). | ||
* Extension for `micromark` that can be passed in `extensions`, to | ||
* enable frontmatter syntax. | ||
*/ | ||
export function frontmatter(options) { | ||
const settings = matters(options) | ||
const listOfMatters = matters(options) | ||
/** @type {ConstructRecord} */ | ||
const flow = {} | ||
let index = -1 | ||
while (++index < settings.length) { | ||
const matter = settings[index] | ||
while (++index < listOfMatters.length) { | ||
const matter = listOfMatters[index] | ||
const code = fence(matter, 'open').charCodeAt(0) | ||
if (code in flow) { | ||
// @ts-expect-error it clearly does exist. | ||
flow[code].push(parse(matter)) | ||
const construct = createConstruct(matter) | ||
const existing = flow[code] | ||
if (Array.isArray(existing)) { | ||
existing.push(construct) | ||
} else { | ||
flow[code] = [parse(matter)] | ||
// Never a single object, always an array. | ||
flow[code] = [construct] | ||
} | ||
@@ -55,14 +53,20 @@ } | ||
*/ | ||
function parse(matter) { | ||
const name = matter.type | ||
function createConstruct(matter) { | ||
const anywhere = matter.anywhere | ||
const valueType = name + 'Value' | ||
const fenceType = name + 'Fence' | ||
const frontmatterType = matter.type | ||
const fenceType = frontmatterType + 'Fence' | ||
const sequenceType = fenceType + 'Sequence' | ||
const fenceConstruct = { | ||
tokenize: tokenizeFence, | ||
const valueType = frontmatterType + 'Value' | ||
const closingFenceConstruct = { | ||
tokenize: tokenizeClosingFence, | ||
partial: true | ||
} | ||
/** @type {string} */ | ||
/** | ||
* Fence to look for. | ||
* | ||
* @type {string} | ||
*/ | ||
let buffer | ||
let bufferIndex = 0 | ||
return { | ||
@@ -81,40 +85,165 @@ tokenize: tokenizeFrontmatter, | ||
/** @type {State} */ | ||
/** | ||
* Start of frontmatter. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function start(code) { | ||
const position = self.now() | ||
if (position.column !== 1 || (!anywhere && position.line !== 1)) { | ||
return nok(code) | ||
if ( | ||
// Indent not allowed. | ||
position.column === 1 && | ||
// Normally, only allowed in first line. | ||
(position.line === 1 || anywhere) | ||
) { | ||
buffer = fence(matter, 'open') | ||
bufferIndex = 0 | ||
if (code === buffer.charCodeAt(bufferIndex)) { | ||
effects.enter(frontmatterType) | ||
effects.enter(fenceType) | ||
effects.enter(sequenceType) | ||
return openSequence(code) | ||
} | ||
} | ||
effects.enter(name) | ||
buffer = fence(matter, 'open') | ||
return effects.attempt(fenceConstruct, afterOpeningFence, nok)(code) | ||
return nok(code) | ||
} | ||
/** @type {State} */ | ||
function afterOpeningFence(code) { | ||
buffer = fence(matter, 'close') | ||
return lineEnd(code) | ||
/** | ||
* In open sequence. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function openSequence(code) { | ||
if (bufferIndex === buffer.length) { | ||
effects.exit(sequenceType) | ||
if (markdownSpace(code)) { | ||
effects.enter('whitespace') | ||
return openSequenceWhitespace(code) | ||
} | ||
return openAfter(code) | ||
} | ||
if (code === buffer.charCodeAt(bufferIndex++)) { | ||
effects.consume(code) | ||
return openSequence | ||
} | ||
return nok(code) | ||
} | ||
/** @type {State} */ | ||
function lineStart(code) { | ||
/** | ||
* In whitespace after open sequence. | ||
* | ||
* ```markdown | ||
* > | ---β | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function openSequenceWhitespace(code) { | ||
if (markdownSpace(code)) { | ||
effects.consume(code) | ||
return openSequenceWhitespace | ||
} | ||
effects.exit('whitespace') | ||
return openAfter(code) | ||
} | ||
/** | ||
* After open sequence. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* ^ | ||
* | title: "Venus" | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function openAfter(code) { | ||
if (markdownLineEnding(code)) { | ||
effects.exit(fenceType) | ||
effects.enter('lineEnding') | ||
effects.consume(code) | ||
effects.exit('lineEnding') | ||
// Get ready for closing fence. | ||
buffer = fence(matter, 'close') | ||
bufferIndex = 0 | ||
return effects.attempt(closingFenceConstruct, after, contentStart) | ||
} | ||
// EOF is not okay. | ||
return nok(code) | ||
} | ||
/** | ||
* Start of content chunk. | ||
* | ||
* ```markdown | ||
* | --- | ||
* > | title: "Venus" | ||
* ^ | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function contentStart(code) { | ||
if (code === null || markdownLineEnding(code)) { | ||
return lineEnd(code) | ||
return contentEnd(code) | ||
} | ||
effects.enter(valueType) | ||
return lineData(code) | ||
return contentInside(code) | ||
} | ||
/** @type {State} */ | ||
function lineData(code) { | ||
/** | ||
* In content chunk. | ||
* | ||
* ```markdown | ||
* | --- | ||
* > | title: "Venus" | ||
* ^ | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function contentInside(code) { | ||
if (code === null || markdownLineEnding(code)) { | ||
effects.exit(valueType) | ||
return lineEnd(code) | ||
return contentEnd(code) | ||
} | ||
effects.consume(code) | ||
return lineData | ||
return contentInside | ||
} | ||
/** @type {State} */ | ||
function lineEnd(code) { | ||
/** | ||
* End of content chunk. | ||
* | ||
* ```markdown | ||
* | --- | ||
* > | title: "Venus" | ||
* ^ | ||
* | --- | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function contentEnd(code) { | ||
// Require a closing fence. | ||
@@ -129,8 +258,20 @@ if (code === null) { | ||
effects.exit('lineEnding') | ||
return effects.attempt(fenceConstruct, after, lineStart) | ||
return effects.attempt(closingFenceConstruct, after, contentStart) | ||
} | ||
/** @type {State} */ | ||
/** | ||
* After frontmatter. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function after(code) { | ||
effects.exit(name) | ||
// `code` must be eol/eof. | ||
effects.exit(frontmatterType) | ||
return ok(code) | ||
@@ -141,12 +282,23 @@ } | ||
/** @type {Tokenizer} */ | ||
function tokenizeFence(effects, ok, nok) { | ||
function tokenizeClosingFence(effects, ok, nok) { | ||
let bufferIndex = 0 | ||
return start | ||
return closeStart | ||
/** @type {State} */ | ||
function start(code) { | ||
/** | ||
* Start of close sequence. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeStart(code) { | ||
if (code === buffer.charCodeAt(bufferIndex)) { | ||
effects.enter(fenceType) | ||
effects.enter(sequenceType) | ||
return insideSequence(code) | ||
return closeSequence(code) | ||
} | ||
@@ -156,4 +308,15 @@ return nok(code) | ||
/** @type {State} */ | ||
function insideSequence(code) { | ||
/** | ||
* In close sequence. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeSequence(code) { | ||
if (bufferIndex === buffer.length) { | ||
@@ -163,9 +326,9 @@ effects.exit(sequenceType) | ||
effects.enter('whitespace') | ||
return insideWhitespace(code) | ||
return closeSequenceWhitespace(code) | ||
} | ||
return fenceEnd(code) | ||
return closeAfter(code) | ||
} | ||
if (code === buffer.charCodeAt(bufferIndex++)) { | ||
effects.consume(code) | ||
return insideSequence | ||
return closeSequence | ||
} | ||
@@ -175,14 +338,36 @@ return nok(code) | ||
/** @type {State} */ | ||
function insideWhitespace(code) { | ||
/** | ||
* In whitespace after close sequence. | ||
* | ||
* ```markdown | ||
* > | --- | ||
* | title: "Venus" | ||
* | ---β | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeSequenceWhitespace(code) { | ||
if (markdownSpace(code)) { | ||
effects.consume(code) | ||
return insideWhitespace | ||
return closeSequenceWhitespace | ||
} | ||
effects.exit('whitespace') | ||
return fenceEnd(code) | ||
return closeAfter(code) | ||
} | ||
/** @type {State} */ | ||
function fenceEnd(code) { | ||
/** | ||
* After close sequence. | ||
* | ||
* ```markdown | ||
* | --- | ||
* | title: "Venus" | ||
* > | --- | ||
* ^ | ||
* ``` | ||
* | ||
* @type {State} | ||
*/ | ||
function closeAfter(code) { | ||
if (code === null || markdownLineEnding(code)) { | ||
@@ -199,3 +384,3 @@ effects.exit(fenceType) | ||
* @param {Matter} matter | ||
* @param {'open'|'close'} prop | ||
* @param {'open' | 'close'} prop | ||
* @returns {string} | ||
@@ -211,4 +396,4 @@ */ | ||
/** | ||
* @param {Info|string} schema | ||
* @param {'open'|'close'} prop | ||
* @param {Info | string} schema | ||
* @param {'open' | 'close'} prop | ||
* @returns {string} | ||
@@ -215,0 +400,0 @@ */ |
/** | ||
* @param {Options} [options='yaml'] | ||
* Simplify one or more options. | ||
* | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Array<Matter>} | ||
* List of matters. | ||
*/ | ||
export function matters(options?: Options | undefined): Array<Matter> | ||
export function matters(options?: Options | null | undefined): Array<Matter> | ||
/** | ||
* Either `'yaml'` or `'toml'`. | ||
* Known name of a frontmatter style. | ||
*/ | ||
export type Preset = 'yaml' | 'toml' | ||
export type Preset = 'toml' | 'yaml' | ||
/** | ||
* Sequence. | ||
* | ||
* Depending on how this structure is used, it reflects a marker or a fence. | ||
*/ | ||
export type Info = { | ||
/** | ||
* Opening. | ||
*/ | ||
open: string | ||
/** | ||
* Closing. | ||
*/ | ||
close: string | ||
} | ||
/** | ||
* Fields describing a kind of matter. | ||
*/ | ||
export type MatterProps = { | ||
/** | ||
* Type to tokenize as. | ||
* Node type to tokenize as. | ||
*/ | ||
type: string | ||
/** | ||
* If `true`, matter can be found anywhere in the document. | ||
* If `false` (default), only matter at the start of the document is | ||
* recognized. | ||
* Whether matter can be found anywhere in the document, normally, only matter | ||
* at the start of the document is recognized. | ||
* | ||
* > π **Note**: using this is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
*/ | ||
anywhere?: boolean | undefined | ||
anywhere?: boolean | null | undefined | ||
} | ||
@@ -31,13 +52,14 @@ /** | ||
/** | ||
* Character used to construct fences. | ||
* By providing an object with `open` and `close` different characters can be | ||
* used for opening and closing fences. | ||
* For example the character `'-'` will result in `'---'` being used as the | ||
* fence | ||
* Character repeated 3 times, used as complete fences. | ||
* | ||
* For example the character `'-'` will result in `'---'` being used as the | ||
* fence | ||
* Pass `open` and `close` to specify different characters for opening and | ||
* closing fences. | ||
*/ | ||
marker: string | Info | ||
marker: Info | string | ||
/** | ||
* If `marker` is set, `fence` must not be set. | ||
*/ | ||
fence?: undefined | ||
fence?: never | ||
} | ||
@@ -49,21 +71,29 @@ /** | ||
/** | ||
* String used as the complete fence. | ||
* By providing an object with `open` and `close` different values can be used | ||
* for opening and closing fences. | ||
* This can be used too if fences contain different characters or lengths | ||
* other than 3. | ||
* Complete fences. | ||
* | ||
* This can be used when fences contain different characters or lengths | ||
* other than 3. | ||
* Pass `open` and `close` to interface to specify different characters for opening and | ||
* closing fences. | ||
*/ | ||
fence: string | Info | ||
fence: Info | string | ||
/** | ||
* If `fence` is set, `marker` must not be set. | ||
*/ | ||
marker?: undefined | ||
marker?: never | ||
} | ||
/** | ||
* Matter object describing frontmatter. | ||
* Fields describing a kind of matter. | ||
* | ||
* > π **Note**: using `anywhere` is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
* | ||
* > π **Note**: `marker` and `fence` are mutually exclusive. | ||
* > If `marker` is set, `fence` must not be set, and vice versa. | ||
*/ | ||
export type Matter = (MatterProps & FenceProps) | (MatterProps & MarkerProps) | ||
/** | ||
* Matter object or preset, or many. | ||
* Configuration. | ||
*/ | ||
export type Options = Preset | Matter | Array<Preset | Matter> | ||
export type Options = Matter | Preset | Array<Matter | Preset> |
/** | ||
* @typedef {'yaml'|'toml'} Preset | ||
* Either `'yaml'` or `'toml'`. | ||
* @typedef {'toml' | 'yaml'} Preset | ||
* Known name of a frontmatter style. | ||
* | ||
* @typedef Info | ||
* Sequence. | ||
* | ||
* Depending on how this structure is used, it reflects a marker or a fence. | ||
* @property {string} open | ||
* Opening. | ||
* @property {string} close | ||
* Closing. | ||
* | ||
* @typedef MatterProps | ||
* Fields describing a kind of matter. | ||
* @property {string} type | ||
* Type to tokenize as. | ||
* @property {boolean} [anywhere=false] | ||
* If `true`, matter can be found anywhere in the document. | ||
* If `false` (default), only matter at the start of the document is | ||
* recognized. | ||
* Node type to tokenize as. | ||
* @property {boolean | null | undefined} [anywhere=false] | ||
* Whether matter can be found anywhere in the document, normally, only matter | ||
* at the start of the document is recognized. | ||
* | ||
* > π **Note**: using this is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
* | ||
* @typedef MarkerProps | ||
* Marker configuration. | ||
* @property {string|Info} marker | ||
* Character used to construct fences. | ||
* By providing an object with `open` and `close` different characters can be | ||
* used for opening and closing fences. | ||
* @property {Info | string} marker | ||
* Character repeated 3 times, used as complete fences. | ||
* | ||
* For example the character `'-'` will result in `'---'` being used as the | ||
* fence | ||
* Pass `open` and `close` to specify different characters for opening and | ||
* closing fences. | ||
* @property {never} [fence] | ||
@@ -30,16 +40,24 @@ * If `marker` is set, `fence` must not be set. | ||
* Fence configuration. | ||
* @property {string|Info} fence | ||
* String used as the complete fence. | ||
* By providing an object with `open` and `close` different values can be used | ||
* for opening and closing fences. | ||
* This can be used too if fences contain different characters or lengths | ||
* @property {Info | string} fence | ||
* Complete fences. | ||
* | ||
* This can be used when fences contain different characters or lengths | ||
* other than 3. | ||
* Pass `open` and `close` to interface to specify different characters for opening and | ||
* closing fences. | ||
* @property {never} [marker] | ||
* If `fence` is set, `marker` must not be set. | ||
* | ||
* @typedef {(MatterProps & FenceProps)|(MatterProps & MarkerProps)} Matter | ||
* Matter object describing frontmatter. | ||
* @typedef {(MatterProps & FenceProps) | (MatterProps & MarkerProps)} Matter | ||
* Fields describing a kind of matter. | ||
* | ||
* @typedef {Preset|Matter|Array<Preset|Matter>} Options | ||
* Matter object or preset, or many. | ||
* > π **Note**: using `anywhere` is a terrible idea. | ||
* > Itβs called frontmatter, not matter-in-the-middle or so. | ||
* > This makes your markdown less portable. | ||
* | ||
* > π **Note**: `marker` and `fence` are mutually exclusive. | ||
* > If `marker` is set, `fence` must not be set, and vice versa. | ||
* | ||
* @typedef {Matter | Preset | Array<Matter | Preset>} Options | ||
* Configuration. | ||
*/ | ||
@@ -55,23 +73,33 @@ | ||
/** | ||
* @param {Options} [options='yaml'] | ||
* Simplify one or more options. | ||
* | ||
* @param {Options | null | undefined} [options='yaml'] | ||
* Configuration. | ||
* @returns {Array<Matter>} | ||
* List of matters. | ||
*/ | ||
export function matters(options = 'yaml') { | ||
export function matters(options) { | ||
/** @type {Array<Matter>} */ | ||
const results = [] | ||
const result = [] | ||
let index = -1 | ||
// One preset or matter. | ||
if (!Array.isArray(options)) { | ||
options = [options] | ||
/** @type {Array<Matter | Preset>} */ | ||
const presetsOrMatters = Array.isArray(options) | ||
? options | ||
: options | ||
? [options] | ||
: ['yaml'] | ||
while (++index < presetsOrMatters.length) { | ||
result[index] = matter(presetsOrMatters[index]) | ||
} | ||
while (++index < options.length) { | ||
results[index] = matter(options[index]) | ||
} | ||
return results | ||
return result | ||
} | ||
/** | ||
* @param {Preset|Matter} option | ||
* Simplify an option. | ||
* | ||
* @param {Matter | Preset} option | ||
* Configuration. | ||
* @returns {Matter} | ||
* Matters. | ||
*/ | ||
@@ -78,0 +106,0 @@ function matter(option) { |
{ | ||
"name": "micromark-extension-frontmatter", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "micromark extension to support frontmatter (YAML, TOML, etc)", | ||
@@ -59,3 +59,3 @@ "license": "MIT", | ||
"devDependencies": { | ||
"@types/tape": "^4.0.0", | ||
"@types/node": "^18.0.0", | ||
"c8": "^7.0.0", | ||
@@ -67,13 +67,12 @@ "micromark": "^3.0.0", | ||
"remark-preset-wooorm": "^9.0.0", | ||
"rimraf": "^3.0.0", | ||
"tape": "^5.0.0", | ||
"type-coverage": "^2.0.0", | ||
"typescript": "^4.0.0", | ||
"typescript": "^5.0.0", | ||
"xo": "^0.53.0" | ||
}, | ||
"scripts": { | ||
"build": "rimraf \"dev/**/*.d.ts\" \"test/**/*.d.ts\" && tsc && type-coverage && micromark-build", | ||
"prepack": "npm run build && npm run format", | ||
"build": "tsc --build --clean && tsc --build && type-coverage && micromark-build", | ||
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", | ||
"test-api": "node --conditions development test/index.js", | ||
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test/index.js", | ||
"test-coverage": "c8 --100 --reporter lcov npm run test-api", | ||
"test": "npm run build && npm run format && npm run test-coverage" | ||
@@ -98,3 +97,3 @@ }, | ||
"plugins": [ | ||
"preset-wooorm" | ||
"remark-preset-wooorm" | ||
] | ||
@@ -101,0 +100,0 @@ }, |
217
readme.md
@@ -11,3 +11,3 @@ # micromark-extension-frontmatter | ||
[micromark][] extension to support frontmatter (YAML, TOML, etc). | ||
[micromark][] extensions to support frontmatter (YAML, TOML, and more). | ||
@@ -23,2 +23,6 @@ ## Contents | ||
* [`frontmatterHtml(options?)`](#frontmatterhtmloptions) | ||
* [`Info`](#info) | ||
* [`Matter`](#matter) | ||
* [`Options`](#options) | ||
* [`Preset`](#preset) | ||
* [Examples](#examples) | ||
@@ -38,24 +42,29 @@ * [Authoring](#authoring) | ||
This package contains extensions that add support for frontmatter. | ||
This package contains two extensions that add support for frontmatter syntax | ||
as often used in markdown to [`micromark`][micromark]. | ||
As there is no spec for frontmatter in markdown, this extension follows how YAML | ||
frontmatter works on github.com. | ||
For the HTML part, instead of rendering YAML, it is ignored. | ||
Other types of frontmatter can be parsed, which will by default also work the | ||
same as on github.com. | ||
Frontmatter is a metadata format in front of the content. | ||
Itβs typically written in YAML and is often used with markdown. | ||
Frontmatter does not work everywhere so it makes markdown less portable. | ||
As there is no spec for frontmatter in markdown, these extensions follow how | ||
YAML frontmatter works on `github.com`. | ||
It can also parse TOML frontmatter, just like YAML except that it uses a `+`. | ||
## When to use this | ||
These tools are all low-level. | ||
In many cases, you want to use [`remark-frontmatter`][plugin] with remark | ||
instead. | ||
You can use these extensions when you are working with [`micromark`][micromark] | ||
already. | ||
When you do want to use `micromark`, you can use this. | ||
When working with `mdast-util-from-markdown`, you must combine this package | ||
with [`mdast-util-frontmatter`][util]. | ||
When you need a syntax tree, you can combine this package with | ||
[`mdast-util-frontmatter`][mdast-util-frontmatter]. | ||
All these packages are used [`remark-frontmatter`][remark-frontmatter], which | ||
focusses on making it easier to transform content by abstracting these | ||
internals away. | ||
## Install | ||
This package is [ESM only][esm]. | ||
In Node.js (version 12.20+, 14.14+, 16.0+, or 18.0+), install with [npm][]: | ||
In Node.js (version 14.14+), install with [npm][]: | ||
@@ -82,2 +91,4 @@ ```sh | ||
Say our module `example.js` looks as follows: | ||
```js | ||
@@ -95,3 +106,3 @@ import {micromark} from 'micromark' | ||
Yields: | ||
β¦now running `node example.js` yields: | ||
@@ -104,6 +115,7 @@ ```html | ||
This package exports the identifiers `frontmatter` and `frontmatterHtml`. | ||
This package exports the identifiers [`frontmatter`][api-frontmatter] and | ||
[`frontmatterHtml`][api-frontmatter-html]. | ||
There is no default export. | ||
The export map supports the endorsed [`development` condition][condition]. | ||
The export map supports the [`development` condition][development]. | ||
Run `node --conditions development module.js` to get instrumented dev code. | ||
@@ -114,60 +126,92 @@ Without this condition, production code is loaded. | ||
Add support for parsing frontmatter in markdown. | ||
Create an extension for [`micromark`][micromark] to enable frontmatter syntax. | ||
Function that can be called to get a syntax extension for micromark (passed | ||
in `extensions`). | ||
###### Parameters | ||
Supports YAML by default. | ||
Can be configured to support TOML and more. | ||
* `options` ([`Options`][api-options], default: `['yaml']`) | ||
β configuration | ||
##### `options` | ||
###### Returns | ||
Configuration (optional). | ||
Extension for `micromark` that can be passed in `extensions`, to enable | ||
frontmatter syntax ([`Extension`][micromark-extension]). | ||
One [`preset`][preset] or [`Matter`][matter], or an array of them, defining all | ||
the supported frontmatters (default: `'yaml'`). | ||
### `frontmatterHtml(options?)` | ||
##### `preset` | ||
Create an extension for `micromark` to support frontmatter when serializing to | ||
HTML. | ||
Either `'yaml'` or `'toml'`: | ||
> π **Note**: this makes sure nothing is generated in the output HTML for | ||
> frontmatter. | ||
* `'yaml'` β [`Matter`][matter] defined as `{type: 'yaml', marker: '-'}` | ||
* `'toml'` β [`Matter`][matter] defined as `{type: 'toml', marker: '+'}` | ||
###### Parameters | ||
##### `Matter` | ||
* `options` ([`Options`][api-options], default: `['yaml']`) | ||
β configuration | ||
An object with a `type` and either a `marker` or a `fence`: | ||
###### Returns | ||
Extension for `micromark` that can be passed in `htmlExtensions`, to support | ||
frontmatter when serializing to HTML | ||
([`HtmlExtension`][micromark-html-extension]). | ||
### `Info` | ||
Sequence (TypeScript type). | ||
Depending on how this structure is used, it reflects a marker or a fence. | ||
###### Fields | ||
* `open` (`string`) | ||
β opening | ||
* `close` (`string`) | ||
β closing | ||
### `Matter` | ||
Fields describing a kind of matter (TypeScript type). | ||
> π **Note**: using `anywhere` is a terrible idea. | ||
> Itβs called frontmatter, not matter-in-the-middle or so. | ||
> This makes your markdown less portable. | ||
> π **Note**: `marker` and `fence` are mutually exclusive. | ||
> If `marker` is set, `fence` must not be set, and vice versa. | ||
###### Fields | ||
* `type` (`string`) | ||
β type to tokenize as | ||
* `marker` (`string` or `{open: string, close: string}`) | ||
β character used to construct fences. | ||
By providing an object with `open` and `close` different characters can be | ||
used for opening and closing fences. | ||
For example the character `'-'` will result in `'---'` being used as the | ||
fence | ||
* `fence` (`string` or `{open: string, close: string}`) | ||
β string used as the complete fence. | ||
By providing an object with `open` and `close` different values can be used | ||
for opening and closing fences. | ||
This can be used too if fences contain different characters or lengths other | ||
than 3 | ||
β node type to tokenize as | ||
* `marker` (`string` or [`Info`][api-info]) | ||
β character repeated 3 times, used as complete fences | ||
* `fence` (`string` or [`Info`][api-info]) | ||
β complete fences | ||
* `anywhere` (`boolean`, default: `false`) | ||
β if `true`, matter can be found anywhere in the document. | ||
If `false` (default), only matter at the start of the document is recognized | ||
β whether matter can be found anywhere in the document, normally only | ||
matter at the start of the document is recognized | ||
### `frontmatterHtml(options?)` | ||
### `Options` | ||
Add support for turning frontmatter in markdown to HTML. | ||
Configuration (TypeScript type). | ||
Function that can be called to get an HTML extension for micromark (passed | ||
in `htmlExtensions`). | ||
###### Type | ||
This makes sure nothing is generated for frontmatter. | ||
```ts | ||
type Options = Matter | Preset | Array<Matter | Preset> | ||
``` | ||
Supports YAML by default. | ||
Can be configured to support other things. | ||
### `Preset` | ||
See `options` above for more info. | ||
Known name of a frontmatter style (TypeScript type). | ||
* `'yaml'` β [`Matter`][api-matter] defined as `{type: 'yaml', marker: '-'}` | ||
* `'toml'` β [`Matter`][api-matter] defined as `{type: 'toml', marker: '+'}` | ||
###### Type | ||
```ts | ||
type Preset = 'toml' | 'yaml' | ||
``` | ||
## Examples | ||
@@ -234,3 +278,3 @@ | ||
Frontmatter does not relate to HTML elements. | ||
It is typically stripped, which is what this plugin does. | ||
It is typically stripped, which is what these extensions do. | ||
@@ -243,20 +287,41 @@ ## CSS | ||
Frontmatter forms with, roughly, the following BNF: | ||
Frontmatter forms with the following BNF: | ||
```bnf | ||
; Note: `fence` is an arbitrary, configured, fence. | ||
frontmatter ::= fence *space_or_tab eol *( *code eol ) fence *space_or_tab | ||
frontmatter ::= fence_open *( eol *line ) eol fence_close | ||
fence_open ::= sequence_open *space_or_tab | ||
fence_close ::= sequence_close *space_or_tab | ||
; Note: options can define custom sequences. | ||
sequence_open ::= 3'+' | 3'-' | ||
; Note: options can define custom sequences. | ||
; Restriction: `sequence_close` must correspond to `sequence_open`. | ||
sequence_close ::= 3'+' | 3'-' | ||
; Character groups for informational purposes. | ||
byte ::= 0x00..=0xFFFF | ||
eol ::= '\n' | '\r' | '\r\n' | ||
line ::= byte - eol | ||
``` | ||
Frontmatter can only occur once. | ||
It cannot occur in a container. | ||
It must have a closing fence. | ||
Like flow constructs, it must be followed by an eol (line ending) or | ||
eof (end of file). | ||
## Types | ||
This package is fully typed with [TypeScript][]. | ||
It exports the additional type `Options`. | ||
It exports the additional types [`Info`][api-info], [`Matter`][api-matter], | ||
[`Options`][api-options], [`Preset`][api-preset]. | ||
## Compatibility | ||
This package is at least compatible with all maintained versions of Node.js. | ||
As of now, that is Node.js 12.20+, 14.14+, 16.0+, and 18.0+. | ||
It also works in Deno and modern browsers. | ||
Projects maintained by the unified collective are compatible with all maintained | ||
versions of Node.js. | ||
As of now, that is Node.js 14.14+. | ||
Our projects sometimes work with older versions, but this is not guaranteed. | ||
These extensions work with `micromark` version 3+. | ||
## Security | ||
@@ -268,5 +333,5 @@ | ||
* [`remarkjs/remark-frontmatter`][plugin] | ||
* [`remark-frontmatter`][remark-frontmatter] | ||
β remark plugin using this to support frontmatter | ||
* [`syntax-tree/mdast-util-frontmatter`][util] | ||
* [`mdast-util-frontmatter`][mdast-util-frontmatter] | ||
β mdast utility to support frontmatter | ||
@@ -334,12 +399,24 @@ | ||
[condition]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions | ||
[development]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions | ||
[micromark]: https://github.com/micromark/micromark | ||
[util]: https://github.com/syntax-tree/mdast-util-frontmatter | ||
[mdast-util-frontmatter]: https://github.com/syntax-tree/mdast-util-frontmatter | ||
[plugin]: https://github.com/remarkjs/remark-frontmatter | ||
[remark-frontmatter]: https://github.com/remarkjs/remark-frontmatter | ||
[preset]: #preset | ||
[micromark-extension]: https://github.com/micromark/micromark#syntaxextension | ||
[matter]: #matter | ||
[micromark-html-extension]: https://github.com/micromark/micromark#htmlextension | ||
[api-frontmatter]: #frontmatteroptions | ||
[api-frontmatter-html]: #frontmatterhtmloptions | ||
[api-info]: #info | ||
[api-matter]: #matter | ||
[api-options]: #options | ||
[api-preset]: #preset |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51250
10
1371
412
1