Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

14
vendor/spatie/ignition/node_modules/micromark/dev/index.d.ts generated vendored Executable file
View File

@@ -0,0 +1,14 @@
/**
* @param value Markdown to parse (`string` or `Buffer`).
* @param [encoding] Character encoding to understand `value` as when its a `Buffer` (`string`, default: `'utf8'`).
* @param [options] Configuration
*/
export const micromark: ((
value: Value,
encoding: Encoding,
options?: Options
) => string) &
((value: Value, options?: Options) => string)
export type Options = import('micromark-util-types').Options
export type Value = import('micromark-util-types').Value
export type Encoding = import('micromark-util-types').Encoding

42
vendor/spatie/ignition/node_modules/micromark/dev/index.js generated vendored Executable file
View File

@@ -0,0 +1,42 @@
/**
* @typedef {import('micromark-util-types').Options} Options
* @typedef {import('micromark-util-types').Value} Value
* @typedef {import('micromark-util-types').Encoding} Encoding
*/
import {compile} from './lib/compile.js'
import {parse} from './lib/parse.js'
import {postprocess} from './lib/postprocess.js'
import {preprocess} from './lib/preprocess.js'
/**
* @param value Markdown to parse (`string` or `Buffer`).
* @param [encoding] Character encoding to understand `value` as when its a `Buffer` (`string`, default: `'utf8'`).
* @param [options] Configuration
*/
export const micromark =
/**
* @type {(
* ((value: Value, encoding: Encoding, options?: Options) => string) &
* ((value: Value, options?: Options) => string)
* )}
*/
(
/**
* @param {Value} value
* @param {Encoding} [encoding]
* @param {Options} [options]
*/
function (value, encoding, options) {
if (typeof encoding !== 'string') {
options = encoding
encoding = undefined
}
return compile(options)(
postprocess(
parse(options).document().write(preprocess()(value, encoding, true))
)
)
}
)

View File

@@ -0,0 +1,25 @@
/**
* @param {CompileOptions} [options]
* @returns {Compile}
*/
export function compile(
options?: import('micromark-util-types').CompileOptions | undefined
): Compile
export type Event = import('micromark-util-types').Event
export type CompileOptions = import('micromark-util-types').CompileOptions
export type CompileData = import('micromark-util-types').CompileData
export type CompileContext = import('micromark-util-types').CompileContext
export type Definition = import('micromark-util-types').Definition
export type Compile = import('micromark-util-types').Compile
export type Handle = import('micromark-util-types').Handle
export type HtmlExtension = import('micromark-util-types').HtmlExtension
export type NormalizedHtmlExtension =
import('micromark-util-types').NormalizedHtmlExtension
export type Media = {
image?: boolean | undefined
labelId?: string | undefined
label?: string | undefined
referenceId?: string | undefined
destination?: string | undefined
title?: string | undefined
}

View File

@@ -0,0 +1,990 @@
/**
* While micromark is a lexer/tokenizer, the common case of going from markdown
* to html is currently built in as this module, even though the parts can be
* used separately to build ASTs, CSTs, or many other output formats.
*
* Having an HTML compiler built in is useful because it allows us to check for
* compliancy to CommonMark, the de facto norm of markdown, specified in roughly
* 600 input/output cases.
*
* This module has an interface that accepts lists of events instead of the
* whole at once, however, because markdown cant be truly streaming, we buffer
* events before processing and outputting the final result.
*/
/**
* @typedef {import('micromark-util-types').Event} Event
* @typedef {import('micromark-util-types').CompileOptions} CompileOptions
* @typedef {import('micromark-util-types').CompileData} CompileData
* @typedef {import('micromark-util-types').CompileContext} CompileContext
* @typedef {import('micromark-util-types').Definition} Definition
* @typedef {import('micromark-util-types').Compile} Compile
* @typedef {import('micromark-util-types').Handle} Handle
* @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
* @typedef {import('micromark-util-types').NormalizedHtmlExtension} NormalizedHtmlExtension
*/
/**
* @typedef Media
* @property {boolean} [image]
* @property {string} [labelId]
* @property {string} [label]
* @property {string} [referenceId]
* @property {string} [destination]
* @property {string} [title]
*/
import {ok as assert} from 'uvu/assert'
import {decodeNamedCharacterReference} from 'decode-named-character-reference'
import {combineHtmlExtensions} from 'micromark-util-combine-extensions'
import {push} from 'micromark-util-chunked'
import {decodeNumericCharacterReference} from 'micromark-util-decode-numeric-character-reference'
import {encode as _encode} from 'micromark-util-encode'
import {normalizeIdentifier} from 'micromark-util-normalize-identifier'
import {sanitizeUri} from 'micromark-util-sanitize-uri'
import {codes} from 'micromark-util-symbol/codes.js'
import {constants} from 'micromark-util-symbol/constants.js'
import {types} from 'micromark-util-symbol/types.js'
const hasOwnProperty = {}.hasOwnProperty
/**
* These two are allowlists of safe protocols for full URLs in respectively the
* `href` (on `<a>`) and `src` (on `<img>`) attributes.
* They are based on what is allowed on GitHub,
* <https://github.com/syntax-tree/hast-util-sanitize/blob/9275b21/lib/github.json#L31>
*/
const protocolHref = /^(https?|ircs?|mailto|xmpp)$/i
const protocolSrc = /^https?$/i
/**
* @param {CompileOptions} [options]
* @returns {Compile}
*/
export function compile(options = {}) {
/**
* Tags is needed because according to markdown, links and emphasis and
* whatnot can exist in images, however, as HTML doesnt allow content in
* images, the tags are ignored in the `alt` attribute, but the content
* remains.
*
* @type {boolean|undefined}
*/
let tags = true
/**
* An object to track identifiers to media (URLs and titles) defined with
* definitions.
*
* @type {Record<string, Definition>}
*/
const definitions = {}
/**
* A lot of the handlers need to capture some of the output data, modify it
* somehow, and then deal with it.
* We do that by tracking a stack of buffers, that can be opened (with
* `buffer`) and closed (with `resume`) to access them.
*
* @type {Array<Array<string>>}
*/
const buffers = [[]]
/**
* As we can have links in images and the other way around, where the deepest
* ones are closed first, we need to track which one were in.
*
* @type {Array<Media>}
*/
const mediaStack = []
/**
* Same as `mediaStack` for tightness, which is specific to lists.
* We need to track if were currently in a tight or loose container.
*
* @type {Array<boolean>}
*/
const tightStack = []
/** @type {HtmlExtension} */
const defaultHandlers = {
enter: {
blockQuote: onenterblockquote,
codeFenced: onentercodefenced,
codeFencedFenceInfo: buffer,
codeFencedFenceMeta: buffer,
codeIndented: onentercodeindented,
codeText: onentercodetext,
content: onentercontent,
definition: onenterdefinition,
definitionDestinationString: onenterdefinitiondestinationstring,
definitionLabelString: buffer,
definitionTitleString: buffer,
emphasis: onenteremphasis,
htmlFlow: onenterhtmlflow,
htmlText: onenterhtml,
image: onenterimage,
label: buffer,
link: onenterlink,
listItemMarker: onenterlistitemmarker,
listItemValue: onenterlistitemvalue,
listOrdered: onenterlistordered,
listUnordered: onenterlistunordered,
paragraph: onenterparagraph,
reference: buffer,
resource: onenterresource,
resourceDestinationString: onenterresourcedestinationstring,
resourceTitleString: buffer,
setextHeading: onentersetextheading,
strong: onenterstrong
},
exit: {
atxHeading: onexitatxheading,
atxHeadingSequence: onexitatxheadingsequence,
autolinkEmail: onexitautolinkemail,
autolinkProtocol: onexitautolinkprotocol,
blockQuote: onexitblockquote,
characterEscapeValue: onexitdata,
characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
characterReferenceValue: onexitcharacterreferencevalue,
codeFenced: onexitflowcode,
codeFencedFence: onexitcodefencedfence,
codeFencedFenceInfo: onexitcodefencedfenceinfo,
codeFencedFenceMeta: resume,
codeFlowValue: onexitcodeflowvalue,
codeIndented: onexitflowcode,
codeText: onexitcodetext,
codeTextData: onexitdata,
data: onexitdata,
definition: onexitdefinition,
definitionDestinationString: onexitdefinitiondestinationstring,
definitionLabelString: onexitdefinitionlabelstring,
definitionTitleString: onexitdefinitiontitlestring,
emphasis: onexitemphasis,
hardBreakEscape: onexithardbreak,
hardBreakTrailing: onexithardbreak,
htmlFlow: onexithtml,
htmlFlowData: onexitdata,
htmlText: onexithtml,
htmlTextData: onexitdata,
image: onexitmedia,
label: onexitlabel,
labelText: onexitlabeltext,
lineEnding: onexitlineending,
link: onexitmedia,
listOrdered: onexitlistordered,
listUnordered: onexitlistunordered,
paragraph: onexitparagraph,
reference: resume,
referenceString: onexitreferencestring,
resource: resume,
resourceDestinationString: onexitresourcedestinationstring,
resourceTitleString: onexitresourcetitlestring,
setextHeading: onexitsetextheading,
setextHeadingLineSequence: onexitsetextheadinglinesequence,
setextHeadingText: onexitsetextheadingtext,
strong: onexitstrong,
thematicBreak: onexitthematicbreak
}
}
/**
* Combine the HTML extensions with the default handlers.
* An HTML extension is an object whose fields are either `enter` or `exit`
* (reflecting whether a token is entered or exited).
* The values at such objects are names of tokens mapping to handlers.
* Handlers are called, respectively when a token is opener or closed, with
* that token, and a context as `this`.
*
* @type {NormalizedHtmlExtension}
*/
// @ts-expect-error `defaultHandlers` is full, so the result will be too.
const handlers = combineHtmlExtensions(
[defaultHandlers].concat(options.htmlExtensions || [])
)
/**
* Handlers do often need to keep track of some state.
* That state is provided here as a key-value store (an object).
*
* @type {CompileData}
*/
const data = {
tightStack,
definitions
}
/**
* The context for handlers references a couple of useful functions.
* In handlers from extensions, those can be accessed at `this`.
* For the handlers here, they can be accessed directly.
*
* @type {Omit<CompileContext, 'sliceSerialize'>}
*/
const context = {
lineEndingIfNeeded,
options,
encode,
raw,
tag,
buffer,
resume,
setData,
getData
}
/**
* Generally, micromark copies line endings (`'\r'`, `'\n'`, `'\r\n'`) in the
* markdown document over to the compiled HTML.
* In some cases, such as `> a`, CommonMark requires that extra line endings
* are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
* This variable hold the default line ending when given (or `undefined`),
* and in the latter case will be updated to the first found line ending if
* there is one.
*/
let lineEndingStyle = options.defaultLineEnding
// Return the function that handles a slice of events.
return compile
/**
* Deal w/ a slice of events.
* Return either the empty string if theres nothing of note to return, or the
* result when done.
*
* @param {Array<Event>} events
* @returns {string}
*/
function compile(events) {
let index = -1
let start = 0
/** @type {Array<number>} */
const listStack = []
// As definitions can come after references, we need to figure out the media
// (urls and titles) defined by them before handling the references.
// So, we do sort of what HTML does: put metadata at the start (in head), and
// then put content after (`body`).
/** @type {Array<Event>} */
let head = []
/** @type {Array<Event>} */
let body = []
while (++index < events.length) {
// Figure out the line ending style used in the document.
if (
!lineEndingStyle &&
(events[index][1].type === types.lineEnding ||
events[index][1].type === types.lineEndingBlank)
) {
// @ts-expect-error Hush, its a line ending.
lineEndingStyle = events[index][2].sliceSerialize(events[index][1])
}
// Preprocess lists to infer whether the list is loose or not.
if (
events[index][1].type === types.listOrdered ||
events[index][1].type === types.listUnordered
) {
if (events[index][0] === 'enter') {
listStack.push(index)
} else {
prepareList(events.slice(listStack.pop(), index))
}
}
// Move definitions to the front.
if (events[index][1].type === types.definition) {
if (events[index][0] === 'enter') {
body = push(body, events.slice(start, index))
start = index
} else {
head = push(head, events.slice(start, index + 1))
start = index + 1
}
}
}
head = push(head, body)
head = push(head, events.slice(start))
index = -1
const result = head
// Handle the start of the document, if defined.
if (handlers.enter.null) {
handlers.enter.null.call(context)
}
// Handle all events.
while (++index < events.length) {
const handler = handlers[result[index][0]]
if (hasOwnProperty.call(handler, result[index][1].type)) {
handler[result[index][1].type].call(
Object.assign(
{sliceSerialize: result[index][2].sliceSerialize},
context
),
result[index][1]
)
}
}
// Handle the end of the document, if defined.
if (handlers.exit.null) {
handlers.exit.null.call(context)
}
return buffers[0].join('')
}
/**
* Figure out whether lists are loose or not.
*
* @param {Array<Event>} slice
* @returns {void}
*/
function prepareList(slice) {
const length = slice.length
let index = 0 // Skip open.
let containerBalance = 0
let loose = false
/** @type {boolean|undefined} */
let atMarker
while (++index < length) {
const event = slice[index]
if (event[1]._container) {
atMarker = undefined
if (event[0] === 'enter') {
containerBalance++
} else {
containerBalance--
}
} else
switch (event[1].type) {
case types.listItemPrefix: {
if (event[0] === 'exit') {
atMarker = true
}
break
}
case types.linePrefix: {
// Ignore
break
}
case types.lineEndingBlank: {
if (event[0] === 'enter' && !containerBalance) {
if (atMarker) {
atMarker = undefined
} else {
loose = true
}
}
break
}
default: {
atMarker = undefined
}
}
}
slice[0][1]._loose = loose
}
/**
* @type {CompileContext['setData']}
* @param [value]
*/
function setData(key, value) {
data[key] = value
}
/**
* @type {CompileContext['getData']}
* @template {string} K
* @param {K} key
* @returns {CompileData[K]}
*/
function getData(key) {
return data[key]
}
/** @type {CompileContext['buffer']} */
function buffer() {
buffers.push([])
}
/** @type {CompileContext['resume']} */
function resume() {
const buf = buffers.pop()
assert(buf !== undefined, 'Cannot resume w/o buffer')
return buf.join('')
}
/** @type {CompileContext['tag']} */
function tag(value) {
if (!tags) return
setData('lastWasTag', true)
buffers[buffers.length - 1].push(value)
}
/** @type {CompileContext['raw']} */
function raw(value) {
setData('lastWasTag')
buffers[buffers.length - 1].push(value)
}
/**
* Output an extra line ending.
*
* @returns {void}
*/
function lineEnding() {
raw(lineEndingStyle || '\n')
}
/** @type {CompileContext['lineEndingIfNeeded']} */
function lineEndingIfNeeded() {
const buffer = buffers[buffers.length - 1]
const slice = buffer[buffer.length - 1]
const previous = slice ? slice.charCodeAt(slice.length - 1) : codes.eof
if (
previous === codes.lf ||
previous === codes.cr ||
previous === codes.eof
) {
return
}
lineEnding()
}
/** @type {CompileContext['encode']} */
function encode(value) {
return getData('ignoreEncode') ? value : _encode(value)
}
//
// Handlers.
//
/** @type {Handle} */
function onenterlistordered(token) {
tightStack.push(!token._loose)
lineEndingIfNeeded()
tag('<ol')
setData('expectFirstItem', true)
}
/** @type {Handle} */
function onenterlistunordered(token) {
tightStack.push(!token._loose)
lineEndingIfNeeded()
tag('<ul')
setData('expectFirstItem', true)
}
/** @type {Handle} */
function onenterlistitemvalue(token) {
if (getData('expectFirstItem')) {
const value = Number.parseInt(
this.sliceSerialize(token),
constants.numericBaseDecimal
)
if (value !== 1) {
tag(' start="' + encode(String(value)) + '"')
}
}
}
/** @type {Handle} */
function onenterlistitemmarker() {
if (getData('expectFirstItem')) {
tag('>')
} else {
onexitlistitem()
}
lineEndingIfNeeded()
tag('<li>')
setData('expectFirstItem')
// “Hack” to prevent a line ending from showing up if the item is empty.
setData('lastWasTag')
}
/** @type {Handle} */
function onexitlistordered() {
onexitlistitem()
tightStack.pop()
lineEnding()
tag('</ol>')
}
/** @type {Handle} */
function onexitlistunordered() {
onexitlistitem()
tightStack.pop()
lineEnding()
tag('</ul>')
}
/** @type {Handle} */
function onexitlistitem() {
if (getData('lastWasTag') && !getData('slurpAllLineEndings')) {
lineEndingIfNeeded()
}
tag('</li>')
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onenterblockquote() {
tightStack.push(false)
lineEndingIfNeeded()
tag('<blockquote>')
}
/** @type {Handle} */
function onexitblockquote() {
tightStack.pop()
lineEndingIfNeeded()
tag('</blockquote>')
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onenterparagraph() {
if (!tightStack[tightStack.length - 1]) {
lineEndingIfNeeded()
tag('<p>')
}
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onexitparagraph() {
if (tightStack[tightStack.length - 1]) {
setData('slurpAllLineEndings', true)
} else {
tag('</p>')
}
}
/** @type {Handle} */
function onentercodefenced() {
lineEndingIfNeeded()
tag('<pre><code')
setData('fencesCount', 0)
}
/** @type {Handle} */
function onexitcodefencedfenceinfo() {
const value = resume()
tag(' class="language-' + value + '"')
}
/** @type {Handle} */
function onexitcodefencedfence() {
const count = getData('fencesCount') || 0
if (!count) {
tag('>')
setData('slurpOneLineEnding', true)
}
setData('fencesCount', count + 1)
}
/** @type {Handle} */
function onentercodeindented() {
lineEndingIfNeeded()
tag('<pre><code>')
}
/** @type {Handle} */
function onexitflowcode() {
const count = getData('fencesCount')
// One special case is if we are inside a container, and the fenced code was
// not closed (meaning it runs to the end).
// In that case, the following line ending, is considered *outside* the
// fenced code and block quote by micromark, but CM wants to treat that
// ending as part of the code.
if (
count !== undefined &&
count < 2 &&
// @ts-expect-error `tightStack` is always set.
data.tightStack.length > 0 &&
!getData('lastWasTag')
) {
lineEnding()
}
// But in most cases, its simpler: when weve seen some data, emit an extra
// line ending when needed.
if (getData('flowCodeSeenData')) {
lineEndingIfNeeded()
}
tag('</code></pre>')
if (count !== undefined && count < 2) lineEndingIfNeeded()
setData('flowCodeSeenData')
setData('fencesCount')
setData('slurpOneLineEnding')
}
/** @type {Handle} */
function onenterimage() {
mediaStack.push({image: true})
tags = undefined // Disallow tags.
}
/** @type {Handle} */
function onenterlink() {
mediaStack.push({})
}
/** @type {Handle} */
function onexitlabeltext(token) {
mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token)
}
/** @type {Handle} */
function onexitlabel() {
mediaStack[mediaStack.length - 1].label = resume()
}
/** @type {Handle} */
function onexitreferencestring(token) {
mediaStack[mediaStack.length - 1].referenceId = this.sliceSerialize(token)
}
/** @type {Handle} */
function onenterresource() {
buffer() // We can have line endings in the resource, ignore them.
mediaStack[mediaStack.length - 1].destination = ''
}
/** @type {Handle} */
function onenterresourcedestinationstring() {
buffer()
// Ignore encoding the result, as well first percent encode the url and
// encode manually after.
setData('ignoreEncode', true)
}
/** @type {Handle} */
function onexitresourcedestinationstring() {
mediaStack[mediaStack.length - 1].destination = resume()
setData('ignoreEncode')
}
/** @type {Handle} */
function onexitresourcetitlestring() {
mediaStack[mediaStack.length - 1].title = resume()
}
/** @type {Handle} */
function onexitmedia() {
let index = mediaStack.length - 1 // Skip current.
const media = mediaStack[index]
const id = media.referenceId || media.labelId
assert(id !== undefined, 'media should have `referenceId` or `labelId`')
assert(media.label !== undefined, 'media should have `label`')
const context =
media.destination === undefined
? definitions[normalizeIdentifier(id)]
: media
tags = true
while (index--) {
if (mediaStack[index].image) {
tags = undefined
break
}
}
if (media.image) {
tag(
'<img src="' +
sanitizeUri(
context.destination,
options.allowDangerousProtocol ? undefined : protocolSrc
) +
'" alt="'
)
raw(media.label)
tag('"')
} else {
tag(
'<a href="' +
sanitizeUri(
context.destination,
options.allowDangerousProtocol ? undefined : protocolHref
) +
'"'
)
}
tag(context.title ? ' title="' + context.title + '"' : '')
if (media.image) {
tag(' />')
} else {
tag('>')
raw(media.label)
tag('</a>')
}
mediaStack.pop()
}
/** @type {Handle} */
function onenterdefinition() {
buffer()
mediaStack.push({})
}
/** @type {Handle} */
function onexitdefinitionlabelstring(token) {
// Discard label, use the source content instead.
resume()
mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token)
}
/** @type {Handle} */
function onenterdefinitiondestinationstring() {
buffer()
setData('ignoreEncode', true)
}
/** @type {Handle} */
function onexitdefinitiondestinationstring() {
mediaStack[mediaStack.length - 1].destination = resume()
setData('ignoreEncode')
}
/** @type {Handle} */
function onexitdefinitiontitlestring() {
mediaStack[mediaStack.length - 1].title = resume()
}
/** @type {Handle} */
function onexitdefinition() {
const media = mediaStack[mediaStack.length - 1]
assert(media.labelId !== undefined, 'media should have `labelId`')
const id = normalizeIdentifier(media.labelId)
resume()
if (!hasOwnProperty.call(definitions, id)) {
definitions[id] = mediaStack[mediaStack.length - 1]
}
mediaStack.pop()
}
/** @type {Handle} */
function onentercontent() {
setData('slurpAllLineEndings', true)
}
/** @type {Handle} */
function onexitatxheadingsequence(token) {
// Exit for further sequences.
if (getData('headingRank')) return
setData('headingRank', this.sliceSerialize(token).length)
lineEndingIfNeeded()
tag('<h' + getData('headingRank') + '>')
}
/** @type {Handle} */
function onentersetextheading() {
buffer()
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onexitsetextheadingtext() {
setData('slurpAllLineEndings', true)
}
/** @type {Handle} */
function onexitatxheading() {
tag('</h' + getData('headingRank') + '>')
setData('headingRank')
}
/** @type {Handle} */
function onexitsetextheadinglinesequence(token) {
setData(
'headingRank',
this.sliceSerialize(token).charCodeAt(0) === codes.equalsTo ? 1 : 2
)
}
/** @type {Handle} */
function onexitsetextheading() {
const value = resume()
lineEndingIfNeeded()
tag('<h' + getData('headingRank') + '>')
raw(value)
tag('</h' + getData('headingRank') + '>')
setData('slurpAllLineEndings')
setData('headingRank')
}
/** @type {Handle} */
function onexitdata(token) {
raw(encode(this.sliceSerialize(token)))
}
/** @type {Handle} */
function onexitlineending(token) {
if (getData('slurpAllLineEndings')) {
return
}
if (getData('slurpOneLineEnding')) {
setData('slurpOneLineEnding')
return
}
if (getData('inCodeText')) {
raw(' ')
return
}
raw(encode(this.sliceSerialize(token)))
}
/** @type {Handle} */
function onexitcodeflowvalue(token) {
raw(encode(this.sliceSerialize(token)))
setData('flowCodeSeenData', true)
}
/** @type {Handle} */
function onexithardbreak() {
tag('<br />')
}
/** @type {Handle} */
function onenterhtmlflow() {
lineEndingIfNeeded()
onenterhtml()
}
/** @type {Handle} */
function onexithtml() {
setData('ignoreEncode')
}
/** @type {Handle} */
function onenterhtml() {
if (options.allowDangerousHtml) {
setData('ignoreEncode', true)
}
}
/** @type {Handle} */
function onenteremphasis() {
tag('<em>')
}
/** @type {Handle} */
function onenterstrong() {
tag('<strong>')
}
/** @type {Handle} */
function onentercodetext() {
setData('inCodeText', true)
tag('<code>')
}
/** @type {Handle} */
function onexitcodetext() {
setData('inCodeText')
tag('</code>')
}
/** @type {Handle} */
function onexitemphasis() {
tag('</em>')
}
/** @type {Handle} */
function onexitstrong() {
tag('</strong>')
}
/** @type {Handle} */
function onexitthematicbreak() {
lineEndingIfNeeded()
tag('<hr />')
}
/** @type {Handle} */
function onexitcharacterreferencemarker(token) {
setData('characterReferenceType', token.type)
}
/** @type {Handle} */
function onexitcharacterreferencevalue(token) {
let value = this.sliceSerialize(token)
// @ts-expect-error `decodeNamedCharacterReference` can return false for
// invalid named character references, but everything weve tokenized is
// valid.
value = getData('characterReferenceType')
? decodeNumericCharacterReference(
value,
getData('characterReferenceType') ===
types.characterReferenceMarkerNumeric
? constants.numericBaseDecimal
: constants.numericBaseHexadecimal
)
: decodeNamedCharacterReference(value)
raw(encode(value))
setData('characterReferenceType')
}
/** @type {Handle} */
function onexitautolinkprotocol(token) {
const uri = this.sliceSerialize(token)
tag(
'<a href="' +
sanitizeUri(
uri,
options.allowDangerousProtocol ? undefined : protocolHref
) +
'">'
)
raw(encode(uri))
tag('</a>')
}
/** @type {Handle} */
function onexitautolinkemail(token) {
const uri = this.sliceSerialize(token)
tag('<a href="' + sanitizeUri('mailto:' + uri) + '">')
raw(encode(uri))
tag('</a>')
}
}

View File

@@ -0,0 +1,19 @@
/** @type {Extension['document']} */
export const document: Extension['document']
/** @type {Extension['contentInitial']} */
export const contentInitial: Extension['contentInitial']
/** @type {Extension['flowInitial']} */
export const flowInitial: Extension['flowInitial']
/** @type {Extension['flow']} */
export const flow: Extension['flow']
/** @type {Extension['string']} */
export const string: Extension['string']
/** @type {Extension['text']} */
export const text: Extension['text']
/** @type {Extension['insideSpan']} */
export const insideSpan: Extension['insideSpan']
/** @type {Extension['attentionMarkers']} */
export const attentionMarkers: Extension['attentionMarkers']
/** @type {Extension['disable']} */
export const disable: Extension['disable']
export type Extension = import('micromark-util-types').Extension

View File

@@ -0,0 +1,101 @@
/**
* @typedef {import('micromark-util-types').Extension} Extension
*/
import {
attention,
autolink,
blockQuote,
characterEscape,
characterReference,
codeFenced,
codeIndented,
codeText,
definition,
hardBreakEscape,
headingAtx,
htmlFlow,
htmlText,
labelEnd,
labelStartImage,
labelStartLink,
lineEnding,
list,
setextUnderline,
thematicBreak
} from 'micromark-core-commonmark'
import {codes} from 'micromark-util-symbol/codes.js'
import {resolver as resolveText} from './initialize/text.js'
/** @type {Extension['document']} */
export const document = {
[codes.asterisk]: list,
[codes.plusSign]: list,
[codes.dash]: list,
[codes.digit0]: list,
[codes.digit1]: list,
[codes.digit2]: list,
[codes.digit3]: list,
[codes.digit4]: list,
[codes.digit5]: list,
[codes.digit6]: list,
[codes.digit7]: list,
[codes.digit8]: list,
[codes.digit9]: list,
[codes.greaterThan]: blockQuote
}
/** @type {Extension['contentInitial']} */
export const contentInitial = {
[codes.leftSquareBracket]: definition
}
/** @type {Extension['flowInitial']} */
export const flowInitial = {
[codes.horizontalTab]: codeIndented,
[codes.virtualSpace]: codeIndented,
[codes.space]: codeIndented
}
/** @type {Extension['flow']} */
export const flow = {
[codes.numberSign]: headingAtx,
[codes.asterisk]: thematicBreak,
[codes.dash]: [setextUnderline, thematicBreak],
[codes.lessThan]: htmlFlow,
[codes.equalsTo]: setextUnderline,
[codes.underscore]: thematicBreak,
[codes.graveAccent]: codeFenced,
[codes.tilde]: codeFenced
}
/** @type {Extension['string']} */
export const string = {
[codes.ampersand]: characterReference,
[codes.backslash]: characterEscape
}
/** @type {Extension['text']} */
export const text = {
[codes.carriageReturn]: lineEnding,
[codes.lineFeed]: lineEnding,
[codes.carriageReturnLineFeed]: lineEnding,
[codes.exclamationMark]: labelStartImage,
[codes.ampersand]: characterReference,
[codes.asterisk]: attention,
[codes.lessThan]: [autolink, htmlText],
[codes.leftSquareBracket]: labelStartLink,
[codes.backslash]: [hardBreakEscape, characterEscape],
[codes.rightSquareBracket]: labelEnd,
[codes.underscore]: attention,
[codes.graveAccent]: codeText
}
/** @type {Extension['insideSpan']} */
export const insideSpan = {null: [attention, resolveText]}
/** @type {Extension['attentionMarkers']} */
export const attentionMarkers = {null: [codes.asterisk, codes.underscore]}
/** @type {Extension['disable']} */
export const disable = {null: []}

View File

@@ -0,0 +1,40 @@
/**
* Create a tokenizer.
* Tokenizers deal with one type of data (e.g., containers, flow, text).
* The parser is the object dealing with it all.
* `initialize` works like other constructs, except that only its `tokenize`
* function is used, in which case it doesnt receive an `ok` or `nok`.
* `from` can be given to set the point before the first character, although
* when further lines are indented, they must be set with `defineSkip`.
*
* @param {ParseContext} parser
* @param {InitialConstruct} initialize
* @param {Omit<Point, '_index'|'_bufferIndex'>} [from]
* @returns {TokenizeContext}
*/
export function createTokenizer(
parser: ParseContext,
initialize: InitialConstruct,
from?:
| Omit<import('micromark-util-types').Point, '_index' | '_bufferIndex'>
| undefined
): TokenizeContext
export type Code = import('micromark-util-types').Code
export type Chunk = import('micromark-util-types').Chunk
export type Point = import('micromark-util-types').Point
export type Token = import('micromark-util-types').Token
export type Effects = import('micromark-util-types').Effects
export type State = import('micromark-util-types').State
export type Construct = import('micromark-util-types').Construct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type ConstructRecord = import('micromark-util-types').ConstructRecord
export type TokenizeContext = import('micromark-util-types').TokenizeContext
export type ParseContext = import('micromark-util-types').ParseContext
export type Info = {
restore: () => void
from: number
}
/**
* Handle a successful run.
*/
export type ReturnHandle = (construct: Construct, info: Info) => void

View File

@@ -0,0 +1,654 @@
/**
* @typedef {import('micromark-util-types').Code} Code
* @typedef {import('micromark-util-types').Chunk} Chunk
* @typedef {import('micromark-util-types').Point} Point
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').Effects} Effects
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').ParseContext} ParseContext
*/
/**
* @typedef Info
* @property {() => void} restore
* @property {number} from
*
* @callback ReturnHandle
* Handle a successful run.
* @param {Construct} construct
* @param {Info} info
* @returns {void}
*/
import {ok as assert} from 'uvu/assert'
import createDebug from 'debug'
import {markdownLineEnding} from 'micromark-util-character'
import {push, splice} from 'micromark-util-chunked'
import {resolveAll} from 'micromark-util-resolve-all'
import {codes} from 'micromark-util-symbol/codes.js'
import {values} from 'micromark-util-symbol/values.js'
const debug = createDebug('micromark')
/**
* Create a tokenizer.
* Tokenizers deal with one type of data (e.g., containers, flow, text).
* The parser is the object dealing with it all.
* `initialize` works like other constructs, except that only its `tokenize`
* function is used, in which case it doesnt receive an `ok` or `nok`.
* `from` can be given to set the point before the first character, although
* when further lines are indented, they must be set with `defineSkip`.
*
* @param {ParseContext} parser
* @param {InitialConstruct} initialize
* @param {Omit<Point, '_index'|'_bufferIndex'>} [from]
* @returns {TokenizeContext}
*/
export function createTokenizer(parser, initialize, from) {
/** @type {Point} */
let point = Object.assign(
from ? Object.assign({}, from) : {line: 1, column: 1, offset: 0},
{_index: 0, _bufferIndex: -1}
)
/** @type {Record<string, number>} */
const columnStart = {}
/** @type {Array<Construct>} */
const resolveAllConstructs = []
/** @type {Array<Chunk>} */
let chunks = []
/** @type {Array<Token>} */
let stack = []
/** @type {boolean|undefined} */
let consumed = true
/**
* Tools used for tokenizing.
*
* @type {Effects}
*/
const effects = {
consume,
enter,
exit,
attempt: constructFactory(onsuccessfulconstruct),
check: constructFactory(onsuccessfulcheck),
interrupt: constructFactory(onsuccessfulcheck, {interrupt: true})
}
/**
* State and tools for resolving and serializing.
*
* @type {TokenizeContext}
*/
const context = {
previous: codes.eof,
code: codes.eof,
containerState: {},
events: [],
parser,
sliceStream,
sliceSerialize,
now,
defineSkip,
write
}
/**
* The state function.
*
* @type {State|void}
*/
let state = initialize.tokenize.call(context, effects)
/**
* Track which character we expect to be consumed, to catch bugs.
*
* @type {Code}
*/
let expectedCode
if (initialize.resolveAll) {
resolveAllConstructs.push(initialize)
}
return context
/** @type {TokenizeContext['write']} */
function write(slice) {
chunks = push(chunks, slice)
main()
// Exit if were not done, resolve might change stuff.
if (chunks[chunks.length - 1] !== codes.eof) {
return []
}
addResult(initialize, 0)
// Otherwise, resolve, and exit.
context.events = resolveAll(resolveAllConstructs, context.events, context)
return context.events
}
//
// Tools.
//
/** @type {TokenizeContext['sliceSerialize']} */
function sliceSerialize(token, expandTabs) {
return serializeChunks(sliceStream(token), expandTabs)
}
/** @type {TokenizeContext['sliceStream']} */
function sliceStream(token) {
return sliceChunks(chunks, token)
}
/** @type {TokenizeContext['now']} */
function now() {
return Object.assign({}, point)
}
/** @type {TokenizeContext['defineSkip']} */
function defineSkip(value) {
columnStart[value.line] = value.column
accountForPotentialSkip()
debug('position: define skip: `%j`', point)
}
//
// State management.
//
/**
* Main loop (note that `_index` and `_bufferIndex` in `point` are modified by
* `consume`).
* Here is where we walk through the chunks, which either include strings of
* several characters, or numerical character codes.
* The reason to do this in a loop instead of a call is so the stack can
* drain.
*
* @returns {void}
*/
function main() {
/** @type {number} */
let chunkIndex
while (point._index < chunks.length) {
const chunk = chunks[point._index]
// If were in a buffer chunk, loop through it.
if (typeof chunk === 'string') {
chunkIndex = point._index
if (point._bufferIndex < 0) {
point._bufferIndex = 0
}
while (
point._index === chunkIndex &&
point._bufferIndex < chunk.length
) {
go(chunk.charCodeAt(point._bufferIndex))
}
} else {
go(chunk)
}
}
}
/**
* Deal with one code.
*
* @param {Code} code
* @returns {void}
*/
function go(code) {
assert(consumed === true, 'expected character to be consumed')
consumed = undefined
debug('main: passing `%s` to %s', code, state && state.name)
expectedCode = code
assert(typeof state === 'function', 'expected state')
state = state(code)
}
/** @type {Effects['consume']} */
function consume(code) {
assert(code === expectedCode, 'expected given code to equal expected code')
debug('consume: `%s`', code)
assert(
consumed === undefined,
'expected code to not have been consumed: this might be because `return x(code)` instead of `return x` was used'
)
assert(
code === null
? context.events.length === 0 ||
context.events[context.events.length - 1][0] === 'exit'
: context.events[context.events.length - 1][0] === 'enter',
'expected last token to be open'
)
if (markdownLineEnding(code)) {
point.line++
point.column = 1
point.offset += code === codes.carriageReturnLineFeed ? 2 : 1
accountForPotentialSkip()
debug('position: after eol: `%j`', point)
} else if (code !== codes.virtualSpace) {
point.column++
point.offset++
}
// Not in a string chunk.
if (point._bufferIndex < 0) {
point._index++
} else {
point._bufferIndex++
// At end of string chunk.
// @ts-expect-error Points w/ non-negative `_bufferIndex` reference
// strings.
if (point._bufferIndex === chunks[point._index].length) {
point._bufferIndex = -1
point._index++
}
}
// Expose the previous character.
context.previous = code
// Mark as consumed.
consumed = true
}
/** @type {Effects['enter']} */
function enter(type, fields) {
/** @type {Token} */
// @ts-expect-error Patch instead of assign required fields to help GC.
const token = fields || {}
token.type = type
token.start = now()
assert(typeof type === 'string', 'expected string type')
assert(type.length > 0, 'expected non-empty string')
debug('enter: `%s`', type)
context.events.push(['enter', token, context])
stack.push(token)
return token
}
/** @type {Effects['exit']} */
function exit(type) {
assert(typeof type === 'string', 'expected string type')
assert(type.length > 0, 'expected non-empty string')
const token = stack.pop()
assert(token, 'cannot close w/o open tokens')
token.end = now()
assert(type === token.type, 'expected exit token to match current token')
assert(
!(
token.start._index === token.end._index &&
token.start._bufferIndex === token.end._bufferIndex
),
'expected non-empty token (`' + type + '`)'
)
debug('exit: `%s`', token.type)
context.events.push(['exit', token, context])
return token
}
/**
* Use results.
*
* @type {ReturnHandle}
*/
function onsuccessfulconstruct(construct, info) {
addResult(construct, info.from)
}
/**
* Discard results.
*
* @type {ReturnHandle}
*/
function onsuccessfulcheck(_, info) {
info.restore()
}
/**
* Factory to attempt/check/interrupt.
*
* @param {ReturnHandle} onreturn
* @param {Record<string, unknown>} [fields]
*/
function constructFactory(onreturn, fields) {
return hook
/**
* Handle either an object mapping codes to constructs, a list of
* constructs, or a single construct.
*
* @param {Construct|Array<Construct>|ConstructRecord} constructs
* @param {State} returnState
* @param {State} [bogusState]
* @returns {State}
*/
function hook(constructs, returnState, bogusState) {
/** @type {Array<Construct>} */
let listOfConstructs
/** @type {number} */
let constructIndex
/** @type {Construct} */
let currentConstruct
/** @type {Info} */
let info
return Array.isArray(constructs)
? /* c8 ignore next 1 */
handleListOfConstructs(constructs)
: 'tokenize' in constructs
? // @ts-expect-error Looks like a construct.
handleListOfConstructs([constructs])
: handleMapOfConstructs(constructs)
/**
* Handle a list of construct.
*
* @param {ConstructRecord} map
* @returns {State}
*/
function handleMapOfConstructs(map) {
return start
/** @type {State} */
function start(code) {
const def = code !== null && map[code]
const all = code !== null && map.null
const list = [
// To do: add more extension tests.
/* c8 ignore next 2 */
...(Array.isArray(def) ? def : def ? [def] : []),
...(Array.isArray(all) ? all : all ? [all] : [])
]
return handleListOfConstructs(list)(code)
}
}
/**
* Handle a list of construct.
*
* @param {Array<Construct>} list
* @returns {State}
*/
function handleListOfConstructs(list) {
listOfConstructs = list
constructIndex = 0
if (list.length === 0) {
assert(bogusState, 'expected `bogusState` to be given')
return bogusState
}
return handleConstruct(list[constructIndex])
}
/**
* Handle a single construct.
*
* @param {Construct} construct
* @returns {State}
*/
function handleConstruct(construct) {
return start
/** @type {State} */
function start(code) {
// To do: not needed to store if there is no bogus state, probably?
// Currently doesnt work because `inspect` in document does a check
// w/o a bogus, which doesnt make sense. But it does seem to help perf
// by not storing.
info = store()
currentConstruct = construct
if (!construct.partial) {
context.currentConstruct = construct
}
if (
construct.name &&
context.parser.constructs.disable.null.includes(construct.name)
) {
return nok(code)
}
return construct.tokenize.call(
// If we do have fields, create an object w/ `context` as its
// prototype.
// This allows a “live binding”, which is needed for `interrupt`.
fields ? Object.assign(Object.create(context), fields) : context,
effects,
ok,
nok
)(code)
}
}
/** @type {State} */
function ok(code) {
assert(code === expectedCode, 'expected code')
consumed = true
onreturn(currentConstruct, info)
return returnState
}
/** @type {State} */
function nok(code) {
assert(code === expectedCode, 'expected code')
consumed = true
info.restore()
if (++constructIndex < listOfConstructs.length) {
return handleConstruct(listOfConstructs[constructIndex])
}
return bogusState
}
}
}
/**
* @param {Construct} construct
* @param {number} from
* @returns {void}
*/
function addResult(construct, from) {
if (construct.resolveAll && !resolveAllConstructs.includes(construct)) {
resolveAllConstructs.push(construct)
}
if (construct.resolve) {
splice(
context.events,
from,
context.events.length - from,
construct.resolve(context.events.slice(from), context)
)
}
if (construct.resolveTo) {
context.events = construct.resolveTo(context.events, context)
}
assert(
construct.partial ||
context.events.length === 0 ||
context.events[context.events.length - 1][0] === 'exit',
'expected last token to end'
)
}
/**
* Store state.
*
* @returns {Info}
*/
function store() {
const startPoint = now()
const startPrevious = context.previous
const startCurrentConstruct = context.currentConstruct
const startEventsIndex = context.events.length
const startStack = Array.from(stack)
return {restore, from: startEventsIndex}
/**
* Restore state.
*
* @returns {void}
*/
function restore() {
point = startPoint
context.previous = startPrevious
context.currentConstruct = startCurrentConstruct
context.events.length = startEventsIndex
stack = startStack
accountForPotentialSkip()
debug('position: restore: `%j`', point)
}
}
/**
* Move the current point a bit forward in the line when its on a column
* skip.
*
* @returns {void}
*/
function accountForPotentialSkip() {
if (point.line in columnStart && point.column < 2) {
point.column = columnStart[point.line]
point.offset += columnStart[point.line] - 1
}
}
}
/**
* Get the chunks from a slice of chunks in the range of a token.
*
* @param {Array<Chunk>} chunks
* @param {Pick<Token, 'start'|'end'>} token
* @returns {Array<Chunk>}
*/
function sliceChunks(chunks, token) {
const startIndex = token.start._index
const startBufferIndex = token.start._bufferIndex
const endIndex = token.end._index
const endBufferIndex = token.end._bufferIndex
/** @type {Array<Chunk>} */
let view
if (startIndex === endIndex) {
assert(endBufferIndex > -1, 'expected non-negative end buffer index')
assert(startBufferIndex > -1, 'expected non-negative start buffer index')
// @ts-expect-error `_bufferIndex` is used on string chunks.
view = [chunks[startIndex].slice(startBufferIndex, endBufferIndex)]
} else {
view = chunks.slice(startIndex, endIndex)
if (startBufferIndex > -1) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view[0] = view[0].slice(startBufferIndex)
}
if (endBufferIndex > 0) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view.push(chunks[endIndex].slice(0, endBufferIndex))
}
}
return view
}
/**
* Get the string value of a slice of chunks.
*
* @param {Array<Chunk>} chunks
* @param {boolean} [expandTabs=false]
* @returns {string}
*/
function serializeChunks(chunks, expandTabs) {
let index = -1
/** @type {Array<string>} */
const result = []
/** @type {boolean|undefined} */
let atTab
while (++index < chunks.length) {
const chunk = chunks[index]
/** @type {string} */
let value
if (typeof chunk === 'string') {
value = chunk
} else
switch (chunk) {
case codes.carriageReturn: {
value = values.cr
break
}
case codes.lineFeed: {
value = values.lf
break
}
case codes.carriageReturnLineFeed: {
value = values.cr + values.lf
break
}
case codes.horizontalTab: {
value = expandTabs ? values.space : values.ht
break
}
case codes.virtualSpace: {
if (!expandTabs && atTab) continue
value = values.space
break
}
default: {
assert(typeof chunk === 'number', 'expected number')
// Currently only replacement character.
value = String.fromCharCode(chunk)
}
}
atTab = chunk === codes.horizontalTab
result.push(value)
}
return result.join('')
}

View File

@@ -0,0 +1,6 @@
/** @type {InitialConstruct} */
export const content: InitialConstruct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type Initializer = import('micromark-util-types').Initializer
export type Token = import('micromark-util-types').Token
export type State = import('micromark-util-types').State

View File

@@ -0,0 +1,93 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').State} State
*/
import {ok as assert} from 'uvu/assert'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {codes} from 'micromark-util-symbol/codes.js'
import {constants} from 'micromark-util-symbol/constants.js'
import {types} from 'micromark-util-symbol/types.js'
/** @type {InitialConstruct} */
export const content = {tokenize: initializeContent}
/** @type {Initializer} */
function initializeContent(effects) {
const contentStart = effects.attempt(
this.parser.constructs.contentInitial,
afterContentStartConstruct,
paragraphInitial
)
/** @type {Token} */
let previous
return contentStart
/** @type {State} */
function afterContentStartConstruct(code) {
assert(
code === codes.eof || markdownLineEnding(code),
'expected eol or eof'
)
if (code === codes.eof) {
effects.consume(code)
return
}
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return factorySpace(effects, contentStart, types.linePrefix)
}
/** @type {State} */
function paragraphInitial(code) {
assert(
code !== codes.eof && !markdownLineEnding(code),
'expected anything other than a line ending or EOF'
)
effects.enter(types.paragraph)
return lineStart(code)
}
/** @type {State} */
function lineStart(code) {
const token = effects.enter(types.chunkText, {
contentType: constants.contentTypeText,
previous
})
if (previous) {
previous.next = token
}
previous = token
return data(code)
}
/** @type {State} */
function data(code) {
if (code === codes.eof) {
effects.exit(types.chunkText)
effects.exit(types.paragraph)
effects.consume(code)
return
}
if (markdownLineEnding(code)) {
effects.consume(code)
effects.exit(types.chunkText)
return lineStart
}
// Data.
effects.consume(code)
return data
}
}

View File

@@ -0,0 +1,12 @@
/** @type {InitialConstruct} */
export const document: InitialConstruct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type Initializer = import('micromark-util-types').Initializer
export type Construct = import('micromark-util-types').Construct
export type TokenizeContext = import('micromark-util-types').TokenizeContext
export type Tokenizer = import('micromark-util-types').Tokenizer
export type Token = import('micromark-util-types').Token
export type State = import('micromark-util-types').State
export type Point = import('micromark-util-types').Point
export type StackState = Record<string, unknown>
export type StackItem = [Construct, StackState]

View File

@@ -0,0 +1,422 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Point} Point
*/
/**
* @typedef {Record<string, unknown>} StackState
* @typedef {[Construct, StackState]} StackItem
*/
import {ok as assert} from 'uvu/assert'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {codes} from 'micromark-util-symbol/codes.js'
import {constants} from 'micromark-util-symbol/constants.js'
import {types} from 'micromark-util-symbol/types.js'
import {splice} from 'micromark-util-chunked'
/** @type {InitialConstruct} */
export const document = {tokenize: initializeDocument}
/** @type {Construct} */
const containerConstruct = {tokenize: tokenizeContainer}
/** @type {Initializer} */
function initializeDocument(effects) {
const self = this
/** @type {Array<StackItem>} */
const stack = []
let continued = 0
/** @type {TokenizeContext|undefined} */
let childFlow
/** @type {Token|undefined} */
let childToken
/** @type {number} */
let lineStartOffset
return start
/** @type {State} */
function start(code) {
// First we iterate through the open blocks, starting with the root
// document, and descending through last children down to the last open
// block.
// Each block imposes a condition that the line must satisfy if the block is
// to remain open.
// For example, a block quote requires a `>` character.
// A paragraph requires a non-blank line.
// In this phase we may match all or just some of the open blocks.
// But we cannot close unmatched blocks yet, because we may have a lazy
// continuation line.
if (continued < stack.length) {
const item = stack[continued]
self.containerState = item[1]
assert(
item[0].continuation,
'expected `continuation` to be defined on container construct'
)
return effects.attempt(
item[0].continuation,
documentContinue,
checkNewContainers
)(code)
}
// Done.
return checkNewContainers(code)
}
/** @type {State} */
function documentContinue(code) {
assert(
self.containerState,
'expected `containerState` to be defined after continuation'
)
continued++
// Note: this field is called `_closeFlow` but it also closes containers.
// Perhaps a good idea to rename it but its already used in the wild by
// extensions.
if (self.containerState._closeFlow) {
self.containerState._closeFlow = undefined
if (childFlow) {
closeFlow()
}
// Note: this algorithm for moving events around is similar to the
// algorithm when dealing with lazy lines in `writeToChild`.
const indexBeforeExits = self.events.length
let indexBeforeFlow = indexBeforeExits
/** @type {Point|undefined} */
let point
// Find the flow chunk.
while (indexBeforeFlow--) {
if (
self.events[indexBeforeFlow][0] === 'exit' &&
self.events[indexBeforeFlow][1].type === types.chunkFlow
) {
point = self.events[indexBeforeFlow][1].end
break
}
}
assert(point, 'could not find previous flow chunk')
exitContainers(continued)
// Fix positions.
let index = indexBeforeExits
while (index < self.events.length) {
self.events[index][1].end = Object.assign({}, point)
index++
}
// Inject the exits earlier (theyre still also at the end).
splice(
self.events,
indexBeforeFlow + 1,
0,
self.events.slice(indexBeforeExits)
)
// Discard the duplicate exits.
self.events.length = index
return checkNewContainers(code)
}
return start(code)
}
/** @type {State} */
function checkNewContainers(code) {
// Next, after consuming the continuation markers for existing blocks, we
// look for new block starts (e.g. `>` for a block quote).
// If we encounter a new block start, we close any blocks unmatched in
// step 1 before creating the new block as a child of the last matched
// block.
if (continued === stack.length) {
// No need to `check` whether theres a container, of `exitContainers`
// would be moot.
// We can instead immediately `attempt` to parse one.
if (!childFlow) {
return documentContinued(code)
}
// If we have concrete content, such as block HTML or fenced code,
// we cant have containers “pierce” into them, so we can immediately
// start.
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) {
return flowStart(code)
}
// If we do have flow, it could still be a blank line,
// but wed be interrupting it w/ a new container if theres a current
// construct.
self.interrupt = Boolean(
childFlow.currentConstruct && !childFlow._gfmTableDynamicInterruptHack
)
}
// Check if there is a new container.
self.containerState = {}
return effects.check(
containerConstruct,
thereIsANewContainer,
thereIsNoNewContainer
)(code)
}
/** @type {State} */
function thereIsANewContainer(code) {
if (childFlow) closeFlow()
exitContainers(continued)
return documentContinued(code)
}
/** @type {State} */
function thereIsNoNewContainer(code) {
self.parser.lazy[self.now().line] = continued !== stack.length
lineStartOffset = self.now().offset
return flowStart(code)
}
/** @type {State} */
function documentContinued(code) {
// Try new containers.
self.containerState = {}
return effects.attempt(
containerConstruct,
containerContinue,
flowStart
)(code)
}
/** @type {State} */
function containerContinue(code) {
assert(
self.currentConstruct,
'expected `currentConstruct` to be defined on tokenizer'
)
assert(
self.containerState,
'expected `containerState` to be defined on tokenizer'
)
continued++
stack.push([self.currentConstruct, self.containerState])
// Try another.
return documentContinued(code)
}
/** @type {State} */
function flowStart(code) {
if (code === codes.eof) {
if (childFlow) closeFlow()
exitContainers(0)
effects.consume(code)
return
}
childFlow = childFlow || self.parser.flow(self.now())
effects.enter(types.chunkFlow, {
contentType: constants.contentTypeFlow,
previous: childToken,
_tokenizer: childFlow
})
return flowContinue(code)
}
/** @type {State} */
function flowContinue(code) {
if (code === codes.eof) {
writeToChild(effects.exit(types.chunkFlow), true)
exitContainers(0)
effects.consume(code)
return
}
if (markdownLineEnding(code)) {
effects.consume(code)
writeToChild(effects.exit(types.chunkFlow))
// Get ready for the next line.
continued = 0
self.interrupt = undefined
return start
}
effects.consume(code)
return flowContinue
}
/**
* @param {Token} token
* @param {boolean} [eof]
* @returns {void}
*/
function writeToChild(token, eof) {
assert(childFlow, 'expected `childFlow` to be defined when continuing')
const stream = self.sliceStream(token)
if (eof) stream.push(null)
token.previous = childToken
if (childToken) childToken.next = token
childToken = token
childFlow.defineSkip(token.start)
childFlow.write(stream)
// Alright, so we just added a lazy line:
//
// ```markdown
// > a
// b.
//
// Or:
//
// > ~~~c
// d
//
// Or:
//
// > | e |
// f
// ```
//
// The construct in the second example (fenced code) does not accept lazy
// lines, so it marked itself as done at the end of its first line, and
// then the content construct parses `d`.
// Most constructs in markdown match on the first line: if the first line
// forms a construct, a non-lazy line cant “unmake” it.
//
// The construct in the third example is potentially a GFM table, and
// those are *weird*.
// It *could* be a table, from the first line, if the following line
// matches a condition.
// In this case, that second line is lazy, which “unmakes” the first line
// and turns the whole into one content block.
//
// Weve now parsed the non-lazy and the lazy line, and can figure out
// whether the lazy line started a new flow block.
// If it did, we exit the current containers between the two flow blocks.
if (self.parser.lazy[token.start.line]) {
let index = childFlow.events.length
while (index--) {
if (
// The token starts before the line ending…
childFlow.events[index][1].start.offset < lineStartOffset &&
// …and either is not ended yet…
(!childFlow.events[index][1].end ||
// …or ends after it.
childFlow.events[index][1].end.offset > lineStartOffset)
) {
// Exit: theres still something open, which means its a lazy line
// part of something.
return
}
}
// Note: this algorithm for moving events around is similar to the
// algorithm when closing flow in `documentContinue`.
const indexBeforeExits = self.events.length
let indexBeforeFlow = indexBeforeExits
/** @type {boolean|undefined} */
let seen
/** @type {Point|undefined} */
let point
// Find the previous chunk (the one before the lazy line).
while (indexBeforeFlow--) {
if (
self.events[indexBeforeFlow][0] === 'exit' &&
self.events[indexBeforeFlow][1].type === types.chunkFlow
) {
if (seen) {
point = self.events[indexBeforeFlow][1].end
break
}
seen = true
}
}
assert(point, 'could not find previous flow chunk')
exitContainers(continued)
// Fix positions.
index = indexBeforeExits
while (index < self.events.length) {
self.events[index][1].end = Object.assign({}, point)
index++
}
// Inject the exits earlier (theyre still also at the end).
splice(
self.events,
indexBeforeFlow + 1,
0,
self.events.slice(indexBeforeExits)
)
// Discard the duplicate exits.
self.events.length = index
}
}
/**
* @param {number} size
* @returns {void}
*/
function exitContainers(size) {
let index = stack.length
// Exit open containers.
while (index-- > size) {
const entry = stack[index]
self.containerState = entry[1]
assert(
entry[0].exit,
'expected `exit` to be defined on container construct'
)
entry[0].exit.call(self, effects)
}
stack.length = size
}
function closeFlow() {
assert(
self.containerState,
'expected `containerState` to be defined when closing flow'
)
assert(childFlow, 'expected `childFlow` to be defined when closing it')
childFlow.write([codes.eof])
childToken = undefined
childFlow = undefined
self.containerState._closeFlow = undefined
}
}
/** @type {Tokenizer} */
function tokenizeContainer(effects, ok, nok) {
return factorySpace(
effects,
effects.attempt(this.parser.constructs.document, ok, nok),
types.linePrefix,
this.parser.constructs.disable.null.includes('codeIndented')
? undefined
: constants.tabSize
)
}

View File

@@ -0,0 +1,5 @@
/** @type {InitialConstruct} */
export const flow: InitialConstruct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type Initializer = import('micromark-util-types').Initializer
export type State = import('micromark-util-types').State

View File

@@ -0,0 +1,79 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').State} State
*/
import {ok as assert} from 'uvu/assert'
import {blankLine, content} from 'micromark-core-commonmark'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {codes} from 'micromark-util-symbol/codes.js'
import {types} from 'micromark-util-symbol/types.js'
/** @type {InitialConstruct} */
export const flow = {tokenize: initializeFlow}
/** @type {Initializer} */
function initializeFlow(effects) {
const self = this
const initial = effects.attempt(
// Try to parse a blank line.
blankLine,
atBlankEnding,
// Try to parse initial flow (essentially, only code).
effects.attempt(
this.parser.constructs.flowInitial,
afterConstruct,
factorySpace(
effects,
effects.attempt(
this.parser.constructs.flow,
afterConstruct,
effects.attempt(content, afterConstruct)
),
types.linePrefix
)
)
)
return initial
/** @type {State} */
function atBlankEnding(code) {
assert(
code === codes.eof || markdownLineEnding(code),
'expected eol or eof'
)
if (code === codes.eof) {
effects.consume(code)
return
}
effects.enter(types.lineEndingBlank)
effects.consume(code)
effects.exit(types.lineEndingBlank)
self.currentConstruct = undefined
return initial
}
/** @type {State} */
function afterConstruct(code) {
assert(
code === codes.eof || markdownLineEnding(code),
'expected eol or eof'
)
if (code === codes.eof) {
effects.consume(code)
return
}
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
self.currentConstruct = undefined
return initial
}
}

View File

@@ -0,0 +1,11 @@
export namespace resolver {
const resolveAll: import('micromark-util-types').Resolver
}
export const string: import('micromark-util-types').InitialConstruct
export const text: import('micromark-util-types').InitialConstruct
export type Resolver = import('micromark-util-types').Resolver
export type Initializer = import('micromark-util-types').Initializer
export type Construct = import('micromark-util-types').Construct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type State = import('micromark-util-types').State
export type Code = import('micromark-util-types').Code

View File

@@ -0,0 +1,225 @@
/**
* @typedef {import('micromark-util-types').Resolver} Resolver
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Code} Code
*/
import {codes} from 'micromark-util-symbol/codes.js'
import {constants} from 'micromark-util-symbol/constants.js'
import {types} from 'micromark-util-symbol/types.js'
export const resolver = {resolveAll: createResolver()}
export const string = initializeFactory('string')
export const text = initializeFactory('text')
/**
* @param {'string'|'text'} field
* @returns {InitialConstruct}
*/
function initializeFactory(field) {
return {
tokenize: initializeText,
resolveAll: createResolver(
field === 'text' ? resolveAllLineSuffixes : undefined
)
}
/** @type {Initializer} */
function initializeText(effects) {
const self = this
const constructs = this.parser.constructs[field]
const text = effects.attempt(constructs, start, notText)
return start
/** @type {State} */
function start(code) {
return atBreak(code) ? text(code) : notText(code)
}
/** @type {State} */
function notText(code) {
if (code === codes.eof) {
effects.consume(code)
return
}
effects.enter(types.data)
effects.consume(code)
return data
}
/** @type {State} */
function data(code) {
if (atBreak(code)) {
effects.exit(types.data)
return text(code)
}
// Data.
effects.consume(code)
return data
}
/**
* @param {Code} code
* @returns {boolean}
*/
function atBreak(code) {
if (code === codes.eof) {
return true
}
const list = constructs[code]
let index = -1
if (list) {
while (++index < list.length) {
const item = list[index]
if (!item.previous || item.previous.call(self, self.previous)) {
return true
}
}
}
return false
}
}
}
/**
* @param {Resolver} [extraResolver]
* @returns {Resolver}
*/
function createResolver(extraResolver) {
return resolveAllText
/** @type {Resolver} */
function resolveAllText(events, context) {
let index = -1
/** @type {number|undefined} */
let enter
// A rather boring computation (to merge adjacent `data` events) which
// improves mm performance by 29%.
while (++index <= events.length) {
if (enter === undefined) {
if (events[index] && events[index][1].type === types.data) {
enter = index
index++
}
} else if (!events[index] || events[index][1].type !== types.data) {
// Dont do anything if there is one data token.
if (index !== enter + 2) {
events[enter][1].end = events[index - 1][1].end
events.splice(enter + 2, index - enter - 2)
index = enter + 2
}
enter = undefined
}
}
return extraResolver ? extraResolver(events, context) : events
}
}
/**
* A rather ugly set of instructions which again looks at chunks in the input
* stream.
* The reason to do this here is that it is *much* faster to parse in reverse.
* And that we cant hook into `null` to split the line suffix before an EOF.
* To do: figure out if we can make this into a clean utility, or even in core.
* As it will be useful for GFMs literal autolink extension (and maybe even
* tables?)
*
* @type {Resolver}
*/
function resolveAllLineSuffixes(events, context) {
let eventIndex = 0 // Skip first.
while (++eventIndex <= events.length) {
if (
(eventIndex === events.length ||
events[eventIndex][1].type === types.lineEnding) &&
events[eventIndex - 1][1].type === types.data
) {
const data = events[eventIndex - 1][1]
const chunks = context.sliceStream(data)
let index = chunks.length
let bufferIndex = -1
let size = 0
/** @type {boolean|undefined} */
let tabs
while (index--) {
const chunk = chunks[index]
if (typeof chunk === 'string') {
bufferIndex = chunk.length
while (chunk.charCodeAt(bufferIndex - 1) === codes.space) {
size++
bufferIndex--
}
if (bufferIndex) break
bufferIndex = -1
}
// Number
else if (chunk === codes.horizontalTab) {
tabs = true
size++
} else if (chunk === codes.virtualSpace) {
// Empty
} else {
// Replacement character, exit.
index++
break
}
}
if (size) {
const token = {
type:
eventIndex === events.length ||
tabs ||
size < constants.hardBreakPrefixSizeMin
? types.lineSuffix
: types.hardBreakTrailing,
start: {
line: data.end.line,
column: data.end.column - size,
offset: data.end.offset - size,
_index: data.start._index + index,
_bufferIndex: index
? bufferIndex
: data.start._bufferIndex + bufferIndex
},
end: Object.assign({}, data.end)
}
data.end = Object.assign({}, token.start)
if (data.start.offset === data.end.offset) {
Object.assign(data, token)
} else {
events.splice(
eventIndex,
0,
['enter', token, context],
['exit', token, context]
)
eventIndex += 2
}
}
eventIndex++
}
}
return events
}

View File

@@ -0,0 +1,13 @@
/**
* @param {ParseOptions} [options]
* @returns {ParseContext}
*/
export function parse(
options?: import('micromark-util-types').ParseOptions | undefined
): ParseContext
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type FullNormalizedExtension =
import('micromark-util-types').FullNormalizedExtension
export type ParseOptions = import('micromark-util-types').ParseOptions
export type ParseContext = import('micromark-util-types').ParseContext
export type Create = import('micromark-util-types').Create

View File

@@ -0,0 +1,52 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').FullNormalizedExtension} FullNormalizedExtension
* @typedef {import('micromark-util-types').ParseOptions} ParseOptions
* @typedef {import('micromark-util-types').ParseContext} ParseContext
* @typedef {import('micromark-util-types').Create} Create
*/
import {combineExtensions} from 'micromark-util-combine-extensions'
import {content} from './initialize/content.js'
import {document} from './initialize/document.js'
import {flow} from './initialize/flow.js'
import {text, string} from './initialize/text.js'
import {createTokenizer} from './create-tokenizer.js'
import * as defaultConstructs from './constructs.js'
/**
* @param {ParseOptions} [options]
* @returns {ParseContext}
*/
export function parse(options = {}) {
/** @type {FullNormalizedExtension} */
// @ts-expect-error `defaultConstructs` is full, so the result will be too.
const constructs = combineExtensions(
// @ts-expect-error Same as above.
[defaultConstructs].concat(options.extensions || [])
)
/** @type {ParseContext} */
const parser = {
defined: [],
lazy: {},
constructs,
content: create(content),
document: create(document),
flow: create(flow),
string: create(string),
text: create(text)
}
return parser
/**
* @param {InitialConstruct} initial
*/
function create(initial) {
return creator
/** @type {Create} */
function creator(from) {
return createTokenizer(parser, initial, from)
}
}
}

View File

@@ -0,0 +1,8 @@
/**
* @param {Array<Event>} events
* @returns {Array<Event>}
*/
export function postprocess(
events: Array<import('micromark-util-types').Event>
): Array<import('micromark-util-types').Event>
export type Event = import('micromark-util-types').Event

View File

@@ -0,0 +1,17 @@
/**
* @typedef {import('micromark-util-types').Event} Event
*/
import {subtokenize} from 'micromark-util-subtokenize'
/**
* @param {Array<Event>} events
* @returns {Array<Event>}
*/
export function postprocess(events) {
while (!subtokenize(events)) {
// Empty
}
return events
}

View File

@@ -0,0 +1,13 @@
/**
* @returns {Preprocessor}
*/
export function preprocess(): Preprocessor
export type Encoding = import('micromark-util-types').Encoding
export type Value = import('micromark-util-types').Value
export type Chunk = import('micromark-util-types').Chunk
export type Code = import('micromark-util-types').Code
export type Preprocessor = (
value: Value,
encoding?: import('micromark-util-types').Encoding | undefined,
end?: boolean | undefined
) => Array<Chunk>

View File

@@ -0,0 +1,133 @@
/**
* @typedef {import('micromark-util-types').Encoding} Encoding
* @typedef {import('micromark-util-types').Value} Value
* @typedef {import('micromark-util-types').Chunk} Chunk
* @typedef {import('micromark-util-types').Code} Code
*/
/**
* @callback Preprocessor
* @param {Value} value
* @param {Encoding} [encoding]
* @param {boolean} [end=false]
* @returns {Array<Chunk>}
*/
import {codes} from 'micromark-util-symbol/codes.js'
import {constants} from 'micromark-util-symbol/constants.js'
const search = /[\0\t\n\r]/g
/**
* @returns {Preprocessor}
*/
export function preprocess() {
let column = 1
let buffer = ''
/** @type {boolean|undefined} */
let start = true
/** @type {boolean|undefined} */
let atCarriageReturn
return preprocessor
/** @type {Preprocessor} */
function preprocessor(value, encoding, end) {
/** @type {Array<Chunk>} */
const chunks = []
/** @type {RegExpMatchArray|null} */
let match
/** @type {number} */
let next
/** @type {number} */
let startPosition
/** @type {number} */
let endPosition
/** @type {Code} */
let code
// @ts-expect-error `Buffer` does allow an encoding.
value = buffer + value.toString(encoding)
startPosition = 0
buffer = ''
if (start) {
if (value.charCodeAt(0) === codes.byteOrderMarker) {
startPosition++
}
start = undefined
}
while (startPosition < value.length) {
search.lastIndex = startPosition
match = search.exec(value)
endPosition =
match && match.index !== undefined ? match.index : value.length
code = value.charCodeAt(endPosition)
if (!match) {
buffer = value.slice(startPosition)
break
}
if (
code === codes.lf &&
startPosition === endPosition &&
atCarriageReturn
) {
chunks.push(codes.carriageReturnLineFeed)
atCarriageReturn = undefined
} else {
if (atCarriageReturn) {
chunks.push(codes.carriageReturn)
atCarriageReturn = undefined
}
if (startPosition < endPosition) {
chunks.push(value.slice(startPosition, endPosition))
column += endPosition - startPosition
}
switch (code) {
case codes.nul: {
chunks.push(codes.replacementCharacter)
column++
break
}
case codes.ht: {
next = Math.ceil(column / constants.tabSize) * constants.tabSize
chunks.push(codes.horizontalTab)
while (column++ < next) chunks.push(codes.virtualSpace)
break
}
case codes.lf: {
chunks.push(codes.lineFeed)
column = 1
break
}
default: {
atCarriageReturn = true
column = 1
}
}
}
startPosition = endPosition + 1
}
if (end) {
if (atCarriageReturn) chunks.push(codes.carriageReturn)
if (buffer) chunks.push(buffer)
chunks.push(codes.eof)
}
return chunks
}
}

View File

@@ -0,0 +1,22 @@
/**
* @param {Options} [options]
* @returns {MinimalDuplex}
*/
export function stream(
options?: import('micromark-util-types').Options | undefined
): MinimalDuplex
export type Options = import('micromark-util-types').Options
export type Value = import('micromark-util-types').Value
export type Encoding = import('micromark-util-types').Encoding
export type Callback = (error?: Error) => void
export type MinimalDuplex = Omit<
NodeJS.ReadableStream & NodeJS.WritableStream,
| 'read'
| 'setEncoding'
| 'pause'
| 'resume'
| 'isPaused'
| 'unpipe'
| 'unshift'
| 'wrap'
>

206
vendor/spatie/ignition/node_modules/micromark/dev/stream.js generated vendored Executable file
View File

@@ -0,0 +1,206 @@
/**
* @typedef {import('micromark-util-types').Options} Options
* @typedef {import('micromark-util-types').Value} Value
* @typedef {import('micromark-util-types').Encoding} Encoding
*/
/**
* @typedef {((error?: Error) => void)} Callback
* @typedef {Omit<NodeJS.ReadableStream & NodeJS.WritableStream, 'read'|'setEncoding'|'pause'|'resume'|'isPaused'|'unpipe'|'unshift'|'wrap'>} MinimalDuplex
*/
import {EventEmitter} from 'events'
import {compile} from './lib/compile.js'
import {parse} from './lib/parse.js'
import {postprocess} from './lib/postprocess.js'
import {preprocess} from './lib/preprocess.js'
/**
* @param {Options} [options]
* @returns {MinimalDuplex}
*/
export function stream(options) {
const prep = preprocess()
const tokenize = parse(options).document().write
const comp = compile(options)
/** @type {boolean} */
let ended
/**
* Write a chunk into memory.
*
* @param {Value} chunk
* @param {Encoding} encoding
* @param {Callback} callback
*/
const write =
/**
* @type {(
* ((value?: Value, encoding?: Encoding, callback?: Callback) => boolean) &
* ((value: Value, callback?: Callback) => boolean)
* )}
*/
(
/**
* @param {Value} [chunk]
* @param {Encoding} [encoding]
* @param {Callback} [callback]
*/
function (chunk, encoding, callback) {
if (typeof encoding === 'function') {
callback = encoding
encoding = undefined
}
if (ended) {
throw new Error('Did not expect `write` after `end`')
}
tokenize(prep(chunk || '', encoding))
if (callback) {
callback()
}
// Signal succesful write.
return true
}
)
/**
* End the writing.
* Passes all arguments to a final `write`.
*
* @param {Value} chunk
* @param {Encoding} encoding
* @param {Callback} callback
*/
const end =
/**
* @type {(
* ((value?: Value, encoding?: Encoding, callback?: Callback) => boolean) &
* ((value: Value, callback?: Callback) => boolean)
* )}
*/
(
/**
* @param {Value} [chunk]
* @param {Encoding} [encoding]
* @param {Callback} [callback]
*/
function (chunk, encoding, callback) {
if (typeof chunk === 'function') {
encoding = chunk
chunk = undefined
}
write(chunk, encoding, callback)
emitter.emit(
'data',
comp(postprocess(tokenize(prep('', encoding, true))))
)
emitter.emit('end')
ended = true
return true
}
)
/** @type {MinimalDuplex} */
// @ts-expect-error `addListener` is fine.
const emitter = Object.assign(new EventEmitter(), {
writable: true,
readable: true,
write,
end,
pipe
})
return emitter
/**
* Pipe the processor into a writable stream.
* Basically `Stream#pipe`, but inlined and simplified to keep the bundled
* size down.
* See: <https://github.com/nodejs/node/blob/43a5170/lib/internal/streams/legacy.js#L13>.
*
* @template {NodeJS.WritableStream} T
* @param {T} dest
* @param {{end?: boolean}} [options]
* @returns {T}
*/
function pipe(dest, options) {
emitter.on('data', ondata)
emitter.on('error', onerror)
emitter.on('end', cleanup)
emitter.on('close', cleanup)
// If the `end` option is not supplied, `dest.end()` will be
// called when the `end` or `close` events are received.
// @ts-expect-error `_isStdio` is available on `std{err,out}`
if (!dest._isStdio && (!options || options.end !== false)) {
emitter.on('end', onend)
}
dest.on('error', onerror)
dest.on('close', cleanup)
dest.emit('pipe', emitter)
return dest
/**
* End destination.
*
* @returns {void}
*/
function onend() {
if (dest.end) {
dest.end()
}
}
/**
* Handle data.
*
* @param {string} chunk
* @returns {void}
*/
function ondata(chunk) {
if (dest.writable) {
dest.write(chunk)
}
}
/**
* Clean listeners.
*
* @returns {void}
*/
function cleanup() {
emitter.removeListener('data', ondata)
emitter.removeListener('end', onend)
emitter.removeListener('error', onerror)
emitter.removeListener('end', cleanup)
emitter.removeListener('close', cleanup)
dest.removeListener('error', onerror)
dest.removeListener('close', cleanup)
}
/**
* Close dangling pipes and handle unheard errors.
*
* @param {Error?} [error]
* @returns {void}
*/
function onerror(error) {
cleanup()
if (!emitter.listenerCount('error')) {
throw error // Unhandled stream error in pipe.
}
}
}
}

14
vendor/spatie/ignition/node_modules/micromark/index.d.ts generated vendored Executable file
View File

@@ -0,0 +1,14 @@
/**
* @param value Markdown to parse (`string` or `Buffer`).
* @param [encoding] Character encoding to understand `value` as when its a `Buffer` (`string`, default: `'utf8'`).
* @param [options] Configuration
*/
export const micromark: ((
value: Value,
encoding: Encoding,
options?: Options
) => string) &
((value: Value, options?: Options) => string)
export type Options = import('micromark-util-types').Options
export type Value = import('micromark-util-types').Value
export type Encoding = import('micromark-util-types').Encoding

40
vendor/spatie/ignition/node_modules/micromark/index.js generated vendored Executable file
View File

@@ -0,0 +1,40 @@
/**
* @typedef {import('micromark-util-types').Options} Options
* @typedef {import('micromark-util-types').Value} Value
* @typedef {import('micromark-util-types').Encoding} Encoding
*/
import {compile} from './lib/compile.js'
import {parse} from './lib/parse.js'
import {postprocess} from './lib/postprocess.js'
import {preprocess} from './lib/preprocess.js'
/**
* @param value Markdown to parse (`string` or `Buffer`).
* @param [encoding] Character encoding to understand `value` as when its a `Buffer` (`string`, default: `'utf8'`).
* @param [options] Configuration
*/
export const micromark =
/**
* @type {(
* ((value: Value, encoding: Encoding, options?: Options) => string) &
* ((value: Value, options?: Options) => string)
* )}
*/
/**
* @param {Value} value
* @param {Encoding} [encoding]
* @param {Options} [options]
*/
function (value, encoding, options) {
if (typeof encoding !== 'string') {
options = encoding
encoding = undefined
}
return compile(options)(
postprocess(
parse(options).document().write(preprocess()(value, encoding, true))
)
)
}

View File

@@ -0,0 +1,25 @@
/**
* @param {CompileOptions} [options]
* @returns {Compile}
*/
export function compile(
options?: import('micromark-util-types').CompileOptions | undefined
): Compile
export type Event = import('micromark-util-types').Event
export type CompileOptions = import('micromark-util-types').CompileOptions
export type CompileData = import('micromark-util-types').CompileData
export type CompileContext = import('micromark-util-types').CompileContext
export type Definition = import('micromark-util-types').Definition
export type Compile = import('micromark-util-types').Compile
export type Handle = import('micromark-util-types').Handle
export type HtmlExtension = import('micromark-util-types').HtmlExtension
export type NormalizedHtmlExtension =
import('micromark-util-types').NormalizedHtmlExtension
export type Media = {
image?: boolean | undefined
labelId?: string | undefined
label?: string | undefined
referenceId?: string | undefined
destination?: string | undefined
title?: string | undefined
}

969
vendor/spatie/ignition/node_modules/micromark/lib/compile.js generated vendored Executable file
View File

@@ -0,0 +1,969 @@
/**
* While micromark is a lexer/tokenizer, the common case of going from markdown
* to html is currently built in as this module, even though the parts can be
* used separately to build ASTs, CSTs, or many other output formats.
*
* Having an HTML compiler built in is useful because it allows us to check for
* compliancy to CommonMark, the de facto norm of markdown, specified in roughly
* 600 input/output cases.
*
* This module has an interface that accepts lists of events instead of the
* whole at once, however, because markdown cant be truly streaming, we buffer
* events before processing and outputting the final result.
*/
/**
* @typedef {import('micromark-util-types').Event} Event
* @typedef {import('micromark-util-types').CompileOptions} CompileOptions
* @typedef {import('micromark-util-types').CompileData} CompileData
* @typedef {import('micromark-util-types').CompileContext} CompileContext
* @typedef {import('micromark-util-types').Definition} Definition
* @typedef {import('micromark-util-types').Compile} Compile
* @typedef {import('micromark-util-types').Handle} Handle
* @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
* @typedef {import('micromark-util-types').NormalizedHtmlExtension} NormalizedHtmlExtension
*/
/**
* @typedef Media
* @property {boolean} [image]
* @property {string} [labelId]
* @property {string} [label]
* @property {string} [referenceId]
* @property {string} [destination]
* @property {string} [title]
*/
import {decodeNamedCharacterReference} from 'decode-named-character-reference'
import {combineHtmlExtensions} from 'micromark-util-combine-extensions'
import {push} from 'micromark-util-chunked'
import {decodeNumericCharacterReference} from 'micromark-util-decode-numeric-character-reference'
import {encode as _encode} from 'micromark-util-encode'
import {normalizeIdentifier} from 'micromark-util-normalize-identifier'
import {sanitizeUri} from 'micromark-util-sanitize-uri'
const hasOwnProperty = {}.hasOwnProperty
/**
* These two are allowlists of safe protocols for full URLs in respectively the
* `href` (on `<a>`) and `src` (on `<img>`) attributes.
* They are based on what is allowed on GitHub,
* <https://github.com/syntax-tree/hast-util-sanitize/blob/9275b21/lib/github.json#L31>
*/
const protocolHref = /^(https?|ircs?|mailto|xmpp)$/i
const protocolSrc = /^https?$/i
/**
* @param {CompileOptions} [options]
* @returns {Compile}
*/
export function compile(options = {}) {
/**
* Tags is needed because according to markdown, links and emphasis and
* whatnot can exist in images, however, as HTML doesnt allow content in
* images, the tags are ignored in the `alt` attribute, but the content
* remains.
*
* @type {boolean|undefined}
*/
let tags = true
/**
* An object to track identifiers to media (URLs and titles) defined with
* definitions.
*
* @type {Record<string, Definition>}
*/
const definitions = {}
/**
* A lot of the handlers need to capture some of the output data, modify it
* somehow, and then deal with it.
* We do that by tracking a stack of buffers, that can be opened (with
* `buffer`) and closed (with `resume`) to access them.
*
* @type {Array<Array<string>>}
*/
const buffers = [[]]
/**
* As we can have links in images and the other way around, where the deepest
* ones are closed first, we need to track which one were in.
*
* @type {Array<Media>}
*/
const mediaStack = []
/**
* Same as `mediaStack` for tightness, which is specific to lists.
* We need to track if were currently in a tight or loose container.
*
* @type {Array<boolean>}
*/
const tightStack = []
/** @type {HtmlExtension} */
const defaultHandlers = {
enter: {
blockQuote: onenterblockquote,
codeFenced: onentercodefenced,
codeFencedFenceInfo: buffer,
codeFencedFenceMeta: buffer,
codeIndented: onentercodeindented,
codeText: onentercodetext,
content: onentercontent,
definition: onenterdefinition,
definitionDestinationString: onenterdefinitiondestinationstring,
definitionLabelString: buffer,
definitionTitleString: buffer,
emphasis: onenteremphasis,
htmlFlow: onenterhtmlflow,
htmlText: onenterhtml,
image: onenterimage,
label: buffer,
link: onenterlink,
listItemMarker: onenterlistitemmarker,
listItemValue: onenterlistitemvalue,
listOrdered: onenterlistordered,
listUnordered: onenterlistunordered,
paragraph: onenterparagraph,
reference: buffer,
resource: onenterresource,
resourceDestinationString: onenterresourcedestinationstring,
resourceTitleString: buffer,
setextHeading: onentersetextheading,
strong: onenterstrong
},
exit: {
atxHeading: onexitatxheading,
atxHeadingSequence: onexitatxheadingsequence,
autolinkEmail: onexitautolinkemail,
autolinkProtocol: onexitautolinkprotocol,
blockQuote: onexitblockquote,
characterEscapeValue: onexitdata,
characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
characterReferenceValue: onexitcharacterreferencevalue,
codeFenced: onexitflowcode,
codeFencedFence: onexitcodefencedfence,
codeFencedFenceInfo: onexitcodefencedfenceinfo,
codeFencedFenceMeta: resume,
codeFlowValue: onexitcodeflowvalue,
codeIndented: onexitflowcode,
codeText: onexitcodetext,
codeTextData: onexitdata,
data: onexitdata,
definition: onexitdefinition,
definitionDestinationString: onexitdefinitiondestinationstring,
definitionLabelString: onexitdefinitionlabelstring,
definitionTitleString: onexitdefinitiontitlestring,
emphasis: onexitemphasis,
hardBreakEscape: onexithardbreak,
hardBreakTrailing: onexithardbreak,
htmlFlow: onexithtml,
htmlFlowData: onexitdata,
htmlText: onexithtml,
htmlTextData: onexitdata,
image: onexitmedia,
label: onexitlabel,
labelText: onexitlabeltext,
lineEnding: onexitlineending,
link: onexitmedia,
listOrdered: onexitlistordered,
listUnordered: onexitlistunordered,
paragraph: onexitparagraph,
reference: resume,
referenceString: onexitreferencestring,
resource: resume,
resourceDestinationString: onexitresourcedestinationstring,
resourceTitleString: onexitresourcetitlestring,
setextHeading: onexitsetextheading,
setextHeadingLineSequence: onexitsetextheadinglinesequence,
setextHeadingText: onexitsetextheadingtext,
strong: onexitstrong,
thematicBreak: onexitthematicbreak
}
}
/**
* Combine the HTML extensions with the default handlers.
* An HTML extension is an object whose fields are either `enter` or `exit`
* (reflecting whether a token is entered or exited).
* The values at such objects are names of tokens mapping to handlers.
* Handlers are called, respectively when a token is opener or closed, with
* that token, and a context as `this`.
*
* @type {NormalizedHtmlExtension}
*/
// @ts-expect-error `defaultHandlers` is full, so the result will be too.
const handlers = combineHtmlExtensions(
[defaultHandlers].concat(options.htmlExtensions || [])
)
/**
* Handlers do often need to keep track of some state.
* That state is provided here as a key-value store (an object).
*
* @type {CompileData}
*/
const data = {
tightStack,
definitions
}
/**
* The context for handlers references a couple of useful functions.
* In handlers from extensions, those can be accessed at `this`.
* For the handlers here, they can be accessed directly.
*
* @type {Omit<CompileContext, 'sliceSerialize'>}
*/
const context = {
lineEndingIfNeeded,
options,
encode,
raw,
tag,
buffer,
resume,
setData,
getData
}
/**
* Generally, micromark copies line endings (`'\r'`, `'\n'`, `'\r\n'`) in the
* markdown document over to the compiled HTML.
* In some cases, such as `> a`, CommonMark requires that extra line endings
* are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
* This variable hold the default line ending when given (or `undefined`),
* and in the latter case will be updated to the first found line ending if
* there is one.
*/
let lineEndingStyle = options.defaultLineEnding // Return the function that handles a slice of events.
return compile
/**
* Deal w/ a slice of events.
* Return either the empty string if theres nothing of note to return, or the
* result when done.
*
* @param {Array<Event>} events
* @returns {string}
*/
function compile(events) {
let index = -1
let start = 0
/** @type {Array<number>} */
const listStack = [] // As definitions can come after references, we need to figure out the media
// (urls and titles) defined by them before handling the references.
// So, we do sort of what HTML does: put metadata at the start (in head), and
// then put content after (`body`).
/** @type {Array<Event>} */
let head = []
/** @type {Array<Event>} */
let body = []
while (++index < events.length) {
// Figure out the line ending style used in the document.
if (
!lineEndingStyle &&
(events[index][1].type === 'lineEnding' ||
events[index][1].type === 'lineEndingBlank')
) {
// @ts-expect-error Hush, its a line ending.
lineEndingStyle = events[index][2].sliceSerialize(events[index][1])
} // Preprocess lists to infer whether the list is loose or not.
if (
events[index][1].type === 'listOrdered' ||
events[index][1].type === 'listUnordered'
) {
if (events[index][0] === 'enter') {
listStack.push(index)
} else {
prepareList(events.slice(listStack.pop(), index))
}
} // Move definitions to the front.
if (events[index][1].type === 'definition') {
if (events[index][0] === 'enter') {
body = push(body, events.slice(start, index))
start = index
} else {
head = push(head, events.slice(start, index + 1))
start = index + 1
}
}
}
head = push(head, body)
head = push(head, events.slice(start))
index = -1
const result = head // Handle the start of the document, if defined.
if (handlers.enter.null) {
handlers.enter.null.call(context)
} // Handle all events.
while (++index < events.length) {
const handler = handlers[result[index][0]]
if (hasOwnProperty.call(handler, result[index][1].type)) {
handler[result[index][1].type].call(
Object.assign(
{
sliceSerialize: result[index][2].sliceSerialize
},
context
),
result[index][1]
)
}
} // Handle the end of the document, if defined.
if (handlers.exit.null) {
handlers.exit.null.call(context)
}
return buffers[0].join('')
}
/**
* Figure out whether lists are loose or not.
*
* @param {Array<Event>} slice
* @returns {void}
*/
function prepareList(slice) {
const length = slice.length
let index = 0 // Skip open.
let containerBalance = 0
let loose = false
/** @type {boolean|undefined} */
let atMarker
while (++index < length) {
const event = slice[index]
if (event[1]._container) {
atMarker = undefined
if (event[0] === 'enter') {
containerBalance++
} else {
containerBalance--
}
} else
switch (event[1].type) {
case 'listItemPrefix': {
if (event[0] === 'exit') {
atMarker = true
}
break
}
case 'linePrefix': {
// Ignore
break
}
case 'lineEndingBlank': {
if (event[0] === 'enter' && !containerBalance) {
if (atMarker) {
atMarker = undefined
} else {
loose = true
}
}
break
}
default: {
atMarker = undefined
}
}
}
slice[0][1]._loose = loose
}
/**
* @type {CompileContext['setData']}
* @param [value]
*/
function setData(key, value) {
data[key] = value
}
/**
* @type {CompileContext['getData']}
* @template {string} K
* @param {K} key
* @returns {CompileData[K]}
*/
function getData(key) {
return data[key]
}
/** @type {CompileContext['buffer']} */
function buffer() {
buffers.push([])
}
/** @type {CompileContext['resume']} */
function resume() {
const buf = buffers.pop()
return buf.join('')
}
/** @type {CompileContext['tag']} */
function tag(value) {
if (!tags) return
setData('lastWasTag', true)
buffers[buffers.length - 1].push(value)
}
/** @type {CompileContext['raw']} */
function raw(value) {
setData('lastWasTag')
buffers[buffers.length - 1].push(value)
}
/**
* Output an extra line ending.
*
* @returns {void}
*/
function lineEnding() {
raw(lineEndingStyle || '\n')
}
/** @type {CompileContext['lineEndingIfNeeded']} */
function lineEndingIfNeeded() {
const buffer = buffers[buffers.length - 1]
const slice = buffer[buffer.length - 1]
const previous = slice ? slice.charCodeAt(slice.length - 1) : null
if (previous === 10 || previous === 13 || previous === null) {
return
}
lineEnding()
}
/** @type {CompileContext['encode']} */
function encode(value) {
return getData('ignoreEncode') ? value : _encode(value)
} //
// Handlers.
//
/** @type {Handle} */
function onenterlistordered(token) {
tightStack.push(!token._loose)
lineEndingIfNeeded()
tag('<ol')
setData('expectFirstItem', true)
}
/** @type {Handle} */
function onenterlistunordered(token) {
tightStack.push(!token._loose)
lineEndingIfNeeded()
tag('<ul')
setData('expectFirstItem', true)
}
/** @type {Handle} */
function onenterlistitemvalue(token) {
if (getData('expectFirstItem')) {
const value = Number.parseInt(this.sliceSerialize(token), 10)
if (value !== 1) {
tag(' start="' + encode(String(value)) + '"')
}
}
}
/** @type {Handle} */
function onenterlistitemmarker() {
if (getData('expectFirstItem')) {
tag('>')
} else {
onexitlistitem()
}
lineEndingIfNeeded()
tag('<li>')
setData('expectFirstItem') // “Hack” to prevent a line ending from showing up if the item is empty.
setData('lastWasTag')
}
/** @type {Handle} */
function onexitlistordered() {
onexitlistitem()
tightStack.pop()
lineEnding()
tag('</ol>')
}
/** @type {Handle} */
function onexitlistunordered() {
onexitlistitem()
tightStack.pop()
lineEnding()
tag('</ul>')
}
/** @type {Handle} */
function onexitlistitem() {
if (getData('lastWasTag') && !getData('slurpAllLineEndings')) {
lineEndingIfNeeded()
}
tag('</li>')
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onenterblockquote() {
tightStack.push(false)
lineEndingIfNeeded()
tag('<blockquote>')
}
/** @type {Handle} */
function onexitblockquote() {
tightStack.pop()
lineEndingIfNeeded()
tag('</blockquote>')
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onenterparagraph() {
if (!tightStack[tightStack.length - 1]) {
lineEndingIfNeeded()
tag('<p>')
}
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onexitparagraph() {
if (tightStack[tightStack.length - 1]) {
setData('slurpAllLineEndings', true)
} else {
tag('</p>')
}
}
/** @type {Handle} */
function onentercodefenced() {
lineEndingIfNeeded()
tag('<pre><code')
setData('fencesCount', 0)
}
/** @type {Handle} */
function onexitcodefencedfenceinfo() {
const value = resume()
tag(' class="language-' + value + '"')
}
/** @type {Handle} */
function onexitcodefencedfence() {
const count = getData('fencesCount') || 0
if (!count) {
tag('>')
setData('slurpOneLineEnding', true)
}
setData('fencesCount', count + 1)
}
/** @type {Handle} */
function onentercodeindented() {
lineEndingIfNeeded()
tag('<pre><code>')
}
/** @type {Handle} */
function onexitflowcode() {
const count = getData('fencesCount') // One special case is if we are inside a container, and the fenced code was
// not closed (meaning it runs to the end).
// In that case, the following line ending, is considered *outside* the
// fenced code and block quote by micromark, but CM wants to treat that
// ending as part of the code.
if (
count !== undefined &&
count < 2 && // @ts-expect-error `tightStack` is always set.
data.tightStack.length > 0 &&
!getData('lastWasTag')
) {
lineEnding()
} // But in most cases, its simpler: when weve seen some data, emit an extra
// line ending when needed.
if (getData('flowCodeSeenData')) {
lineEndingIfNeeded()
}
tag('</code></pre>')
if (count !== undefined && count < 2) lineEndingIfNeeded()
setData('flowCodeSeenData')
setData('fencesCount')
setData('slurpOneLineEnding')
}
/** @type {Handle} */
function onenterimage() {
mediaStack.push({
image: true
})
tags = undefined // Disallow tags.
}
/** @type {Handle} */
function onenterlink() {
mediaStack.push({})
}
/** @type {Handle} */
function onexitlabeltext(token) {
mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token)
}
/** @type {Handle} */
function onexitlabel() {
mediaStack[mediaStack.length - 1].label = resume()
}
/** @type {Handle} */
function onexitreferencestring(token) {
mediaStack[mediaStack.length - 1].referenceId = this.sliceSerialize(token)
}
/** @type {Handle} */
function onenterresource() {
buffer() // We can have line endings in the resource, ignore them.
mediaStack[mediaStack.length - 1].destination = ''
}
/** @type {Handle} */
function onenterresourcedestinationstring() {
buffer() // Ignore encoding the result, as well first percent encode the url and
// encode manually after.
setData('ignoreEncode', true)
}
/** @type {Handle} */
function onexitresourcedestinationstring() {
mediaStack[mediaStack.length - 1].destination = resume()
setData('ignoreEncode')
}
/** @type {Handle} */
function onexitresourcetitlestring() {
mediaStack[mediaStack.length - 1].title = resume()
}
/** @type {Handle} */
function onexitmedia() {
let index = mediaStack.length - 1 // Skip current.
const media = mediaStack[index]
const id = media.referenceId || media.labelId
const context =
media.destination === undefined
? definitions[normalizeIdentifier(id)]
: media
tags = true
while (index--) {
if (mediaStack[index].image) {
tags = undefined
break
}
}
if (media.image) {
tag(
'<img src="' +
sanitizeUri(
context.destination,
options.allowDangerousProtocol ? undefined : protocolSrc
) +
'" alt="'
)
raw(media.label)
tag('"')
} else {
tag(
'<a href="' +
sanitizeUri(
context.destination,
options.allowDangerousProtocol ? undefined : protocolHref
) +
'"'
)
}
tag(context.title ? ' title="' + context.title + '"' : '')
if (media.image) {
tag(' />')
} else {
tag('>')
raw(media.label)
tag('</a>')
}
mediaStack.pop()
}
/** @type {Handle} */
function onenterdefinition() {
buffer()
mediaStack.push({})
}
/** @type {Handle} */
function onexitdefinitionlabelstring(token) {
// Discard label, use the source content instead.
resume()
mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token)
}
/** @type {Handle} */
function onenterdefinitiondestinationstring() {
buffer()
setData('ignoreEncode', true)
}
/** @type {Handle} */
function onexitdefinitiondestinationstring() {
mediaStack[mediaStack.length - 1].destination = resume()
setData('ignoreEncode')
}
/** @type {Handle} */
function onexitdefinitiontitlestring() {
mediaStack[mediaStack.length - 1].title = resume()
}
/** @type {Handle} */
function onexitdefinition() {
const media = mediaStack[mediaStack.length - 1]
const id = normalizeIdentifier(media.labelId)
resume()
if (!hasOwnProperty.call(definitions, id)) {
definitions[id] = mediaStack[mediaStack.length - 1]
}
mediaStack.pop()
}
/** @type {Handle} */
function onentercontent() {
setData('slurpAllLineEndings', true)
}
/** @type {Handle} */
function onexitatxheadingsequence(token) {
// Exit for further sequences.
if (getData('headingRank')) return
setData('headingRank', this.sliceSerialize(token).length)
lineEndingIfNeeded()
tag('<h' + getData('headingRank') + '>')
}
/** @type {Handle} */
function onentersetextheading() {
buffer()
setData('slurpAllLineEndings')
}
/** @type {Handle} */
function onexitsetextheadingtext() {
setData('slurpAllLineEndings', true)
}
/** @type {Handle} */
function onexitatxheading() {
tag('</h' + getData('headingRank') + '>')
setData('headingRank')
}
/** @type {Handle} */
function onexitsetextheadinglinesequence(token) {
setData(
'headingRank',
this.sliceSerialize(token).charCodeAt(0) === 61 ? 1 : 2
)
}
/** @type {Handle} */
function onexitsetextheading() {
const value = resume()
lineEndingIfNeeded()
tag('<h' + getData('headingRank') + '>')
raw(value)
tag('</h' + getData('headingRank') + '>')
setData('slurpAllLineEndings')
setData('headingRank')
}
/** @type {Handle} */
function onexitdata(token) {
raw(encode(this.sliceSerialize(token)))
}
/** @type {Handle} */
function onexitlineending(token) {
if (getData('slurpAllLineEndings')) {
return
}
if (getData('slurpOneLineEnding')) {
setData('slurpOneLineEnding')
return
}
if (getData('inCodeText')) {
raw(' ')
return
}
raw(encode(this.sliceSerialize(token)))
}
/** @type {Handle} */
function onexitcodeflowvalue(token) {
raw(encode(this.sliceSerialize(token)))
setData('flowCodeSeenData', true)
}
/** @type {Handle} */
function onexithardbreak() {
tag('<br />')
}
/** @type {Handle} */
function onenterhtmlflow() {
lineEndingIfNeeded()
onenterhtml()
}
/** @type {Handle} */
function onexithtml() {
setData('ignoreEncode')
}
/** @type {Handle} */
function onenterhtml() {
if (options.allowDangerousHtml) {
setData('ignoreEncode', true)
}
}
/** @type {Handle} */
function onenteremphasis() {
tag('<em>')
}
/** @type {Handle} */
function onenterstrong() {
tag('<strong>')
}
/** @type {Handle} */
function onentercodetext() {
setData('inCodeText', true)
tag('<code>')
}
/** @type {Handle} */
function onexitcodetext() {
setData('inCodeText')
tag('</code>')
}
/** @type {Handle} */
function onexitemphasis() {
tag('</em>')
}
/** @type {Handle} */
function onexitstrong() {
tag('</strong>')
}
/** @type {Handle} */
function onexitthematicbreak() {
lineEndingIfNeeded()
tag('<hr />')
}
/** @type {Handle} */
function onexitcharacterreferencemarker(token) {
setData('characterReferenceType', token.type)
}
/** @type {Handle} */
function onexitcharacterreferencevalue(token) {
let value = this.sliceSerialize(token) // @ts-expect-error `decodeNamedCharacterReference` can return false for
// invalid named character references, but everything weve tokenized is
// valid.
value = getData('characterReferenceType')
? decodeNumericCharacterReference(
value,
getData('characterReferenceType') ===
'characterReferenceMarkerNumeric'
? 10
: 16
)
: decodeNamedCharacterReference(value)
raw(encode(value))
setData('characterReferenceType')
}
/** @type {Handle} */
function onexitautolinkprotocol(token) {
const uri = this.sliceSerialize(token)
tag(
'<a href="' +
sanitizeUri(
uri,
options.allowDangerousProtocol ? undefined : protocolHref
) +
'">'
)
raw(encode(uri))
tag('</a>')
}
/** @type {Handle} */
function onexitautolinkemail(token) {
const uri = this.sliceSerialize(token)
tag('<a href="' + sanitizeUri('mailto:' + uri) + '">')
raw(encode(uri))
tag('</a>')
}
}

View File

@@ -0,0 +1,19 @@
/** @type {Extension['document']} */
export const document: Extension['document']
/** @type {Extension['contentInitial']} */
export const contentInitial: Extension['contentInitial']
/** @type {Extension['flowInitial']} */
export const flowInitial: Extension['flowInitial']
/** @type {Extension['flow']} */
export const flow: Extension['flow']
/** @type {Extension['string']} */
export const string: Extension['string']
/** @type {Extension['text']} */
export const text: Extension['text']
/** @type {Extension['insideSpan']} */
export const insideSpan: Extension['insideSpan']
/** @type {Extension['attentionMarkers']} */
export const attentionMarkers: Extension['attentionMarkers']
/** @type {Extension['disable']} */
export const disable: Extension['disable']
export type Extension = import('micromark-util-types').Extension

View File

@@ -0,0 +1,105 @@
/**
* @typedef {import('micromark-util-types').Extension} Extension
*/
import {
attention,
autolink,
blockQuote,
characterEscape,
characterReference,
codeFenced,
codeIndented,
codeText,
definition,
hardBreakEscape,
headingAtx,
htmlFlow,
htmlText,
labelEnd,
labelStartImage,
labelStartLink,
lineEnding,
list,
setextUnderline,
thematicBreak
} from 'micromark-core-commonmark'
import {resolver as resolveText} from './initialize/text.js'
/** @type {Extension['document']} */
export const document = {
[42]: list,
[43]: list,
[45]: list,
[48]: list,
[49]: list,
[50]: list,
[51]: list,
[52]: list,
[53]: list,
[54]: list,
[55]: list,
[56]: list,
[57]: list,
[62]: blockQuote
}
/** @type {Extension['contentInitial']} */
export const contentInitial = {
[91]: definition
}
/** @type {Extension['flowInitial']} */
export const flowInitial = {
[-2]: codeIndented,
[-1]: codeIndented,
[32]: codeIndented
}
/** @type {Extension['flow']} */
export const flow = {
[35]: headingAtx,
[42]: thematicBreak,
[45]: [setextUnderline, thematicBreak],
[60]: htmlFlow,
[61]: setextUnderline,
[95]: thematicBreak,
[96]: codeFenced,
[126]: codeFenced
}
/** @type {Extension['string']} */
export const string = {
[38]: characterReference,
[92]: characterEscape
}
/** @type {Extension['text']} */
export const text = {
[-5]: lineEnding,
[-4]: lineEnding,
[-3]: lineEnding,
[33]: labelStartImage,
[38]: characterReference,
[42]: attention,
[60]: [autolink, htmlText],
[91]: labelStartLink,
[92]: [hardBreakEscape, characterEscape],
[93]: labelEnd,
[95]: attention,
[96]: codeText
}
/** @type {Extension['insideSpan']} */
export const insideSpan = {
null: [attention, resolveText]
}
/** @type {Extension['attentionMarkers']} */
export const attentionMarkers = {
null: [42, 95]
}
/** @type {Extension['disable']} */
export const disable = {
null: []
}

View File

@@ -0,0 +1,40 @@
/**
* Create a tokenizer.
* Tokenizers deal with one type of data (e.g., containers, flow, text).
* The parser is the object dealing with it all.
* `initialize` works like other constructs, except that only its `tokenize`
* function is used, in which case it doesnt receive an `ok` or `nok`.
* `from` can be given to set the point before the first character, although
* when further lines are indented, they must be set with `defineSkip`.
*
* @param {ParseContext} parser
* @param {InitialConstruct} initialize
* @param {Omit<Point, '_index'|'_bufferIndex'>} [from]
* @returns {TokenizeContext}
*/
export function createTokenizer(
parser: ParseContext,
initialize: InitialConstruct,
from?:
| Omit<import('micromark-util-types').Point, '_index' | '_bufferIndex'>
| undefined
): TokenizeContext
export type Code = import('micromark-util-types').Code
export type Chunk = import('micromark-util-types').Chunk
export type Point = import('micromark-util-types').Point
export type Token = import('micromark-util-types').Token
export type Effects = import('micromark-util-types').Effects
export type State = import('micromark-util-types').State
export type Construct = import('micromark-util-types').Construct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type ConstructRecord = import('micromark-util-types').ConstructRecord
export type TokenizeContext = import('micromark-util-types').TokenizeContext
export type ParseContext = import('micromark-util-types').ParseContext
export type Info = {
restore: () => void
from: number
}
/**
* Handle a successful run.
*/
export type ReturnHandle = (construct: Construct, info: Info) => void

View File

@@ -0,0 +1,595 @@
/**
* @typedef {import('micromark-util-types').Code} Code
* @typedef {import('micromark-util-types').Chunk} Chunk
* @typedef {import('micromark-util-types').Point} Point
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').Effects} Effects
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').ConstructRecord} ConstructRecord
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').ParseContext} ParseContext
*/
/**
* @typedef Info
* @property {() => void} restore
* @property {number} from
*
* @callback ReturnHandle
* Handle a successful run.
* @param {Construct} construct
* @param {Info} info
* @returns {void}
*/
import {markdownLineEnding} from 'micromark-util-character'
import {push, splice} from 'micromark-util-chunked'
import {resolveAll} from 'micromark-util-resolve-all'
/**
* Create a tokenizer.
* Tokenizers deal with one type of data (e.g., containers, flow, text).
* The parser is the object dealing with it all.
* `initialize` works like other constructs, except that only its `tokenize`
* function is used, in which case it doesnt receive an `ok` or `nok`.
* `from` can be given to set the point before the first character, although
* when further lines are indented, they must be set with `defineSkip`.
*
* @param {ParseContext} parser
* @param {InitialConstruct} initialize
* @param {Omit<Point, '_index'|'_bufferIndex'>} [from]
* @returns {TokenizeContext}
*/
export function createTokenizer(parser, initialize, from) {
/** @type {Point} */
let point = Object.assign(
from
? Object.assign({}, from)
: {
line: 1,
column: 1,
offset: 0
},
{
_index: 0,
_bufferIndex: -1
}
)
/** @type {Record<string, number>} */
const columnStart = {}
/** @type {Array<Construct>} */
const resolveAllConstructs = []
/** @type {Array<Chunk>} */
let chunks = []
/** @type {Array<Token>} */
let stack = []
/** @type {boolean|undefined} */
let consumed = true
/**
* Tools used for tokenizing.
*
* @type {Effects}
*/
const effects = {
consume,
enter,
exit,
attempt: constructFactory(onsuccessfulconstruct),
check: constructFactory(onsuccessfulcheck),
interrupt: constructFactory(onsuccessfulcheck, {
interrupt: true
})
}
/**
* State and tools for resolving and serializing.
*
* @type {TokenizeContext}
*/
const context = {
previous: null,
code: null,
containerState: {},
events: [],
parser,
sliceStream,
sliceSerialize,
now,
defineSkip,
write
}
/**
* The state function.
*
* @type {State|void}
*/
let state = initialize.tokenize.call(context, effects)
/**
* Track which character we expect to be consumed, to catch bugs.
*
* @type {Code}
*/
let expectedCode
if (initialize.resolveAll) {
resolveAllConstructs.push(initialize)
}
return context
/** @type {TokenizeContext['write']} */
function write(slice) {
chunks = push(chunks, slice)
main() // Exit if were not done, resolve might change stuff.
if (chunks[chunks.length - 1] !== null) {
return []
}
addResult(initialize, 0) // Otherwise, resolve, and exit.
context.events = resolveAll(resolveAllConstructs, context.events, context)
return context.events
} //
// Tools.
//
/** @type {TokenizeContext['sliceSerialize']} */
function sliceSerialize(token, expandTabs) {
return serializeChunks(sliceStream(token), expandTabs)
}
/** @type {TokenizeContext['sliceStream']} */
function sliceStream(token) {
return sliceChunks(chunks, token)
}
/** @type {TokenizeContext['now']} */
function now() {
return Object.assign({}, point)
}
/** @type {TokenizeContext['defineSkip']} */
function defineSkip(value) {
columnStart[value.line] = value.column
accountForPotentialSkip()
} //
// State management.
//
/**
* Main loop (note that `_index` and `_bufferIndex` in `point` are modified by
* `consume`).
* Here is where we walk through the chunks, which either include strings of
* several characters, or numerical character codes.
* The reason to do this in a loop instead of a call is so the stack can
* drain.
*
* @returns {void}
*/
function main() {
/** @type {number} */
let chunkIndex
while (point._index < chunks.length) {
const chunk = chunks[point._index] // If were in a buffer chunk, loop through it.
if (typeof chunk === 'string') {
chunkIndex = point._index
if (point._bufferIndex < 0) {
point._bufferIndex = 0
}
while (
point._index === chunkIndex &&
point._bufferIndex < chunk.length
) {
go(chunk.charCodeAt(point._bufferIndex))
}
} else {
go(chunk)
}
}
}
/**
* Deal with one code.
*
* @param {Code} code
* @returns {void}
*/
function go(code) {
consumed = undefined
expectedCode = code
state = state(code)
}
/** @type {Effects['consume']} */
function consume(code) {
if (markdownLineEnding(code)) {
point.line++
point.column = 1
point.offset += code === -3 ? 2 : 1
accountForPotentialSkip()
} else if (code !== -1) {
point.column++
point.offset++
} // Not in a string chunk.
if (point._bufferIndex < 0) {
point._index++
} else {
point._bufferIndex++ // At end of string chunk.
// @ts-expect-error Points w/ non-negative `_bufferIndex` reference
// strings.
if (point._bufferIndex === chunks[point._index].length) {
point._bufferIndex = -1
point._index++
}
} // Expose the previous character.
context.previous = code // Mark as consumed.
consumed = true
}
/** @type {Effects['enter']} */
function enter(type, fields) {
/** @type {Token} */
// @ts-expect-error Patch instead of assign required fields to help GC.
const token = fields || {}
token.type = type
token.start = now()
context.events.push(['enter', token, context])
stack.push(token)
return token
}
/** @type {Effects['exit']} */
function exit(type) {
const token = stack.pop()
token.end = now()
context.events.push(['exit', token, context])
return token
}
/**
* Use results.
*
* @type {ReturnHandle}
*/
function onsuccessfulconstruct(construct, info) {
addResult(construct, info.from)
}
/**
* Discard results.
*
* @type {ReturnHandle}
*/
function onsuccessfulcheck(_, info) {
info.restore()
}
/**
* Factory to attempt/check/interrupt.
*
* @param {ReturnHandle} onreturn
* @param {Record<string, unknown>} [fields]
*/
function constructFactory(onreturn, fields) {
return hook
/**
* Handle either an object mapping codes to constructs, a list of
* constructs, or a single construct.
*
* @param {Construct|Array<Construct>|ConstructRecord} constructs
* @param {State} returnState
* @param {State} [bogusState]
* @returns {State}
*/
function hook(constructs, returnState, bogusState) {
/** @type {Array<Construct>} */
let listOfConstructs
/** @type {number} */
let constructIndex
/** @type {Construct} */
let currentConstruct
/** @type {Info} */
let info
return Array.isArray(constructs)
? /* c8 ignore next 1 */
handleListOfConstructs(constructs)
: 'tokenize' in constructs // @ts-expect-error Looks like a construct.
? handleListOfConstructs([constructs])
: handleMapOfConstructs(constructs)
/**
* Handle a list of construct.
*
* @param {ConstructRecord} map
* @returns {State}
*/
function handleMapOfConstructs(map) {
return start
/** @type {State} */
function start(code) {
const def = code !== null && map[code]
const all = code !== null && map.null
const list = [
// To do: add more extension tests.
/* c8 ignore next 2 */
...(Array.isArray(def) ? def : def ? [def] : []),
...(Array.isArray(all) ? all : all ? [all] : [])
]
return handleListOfConstructs(list)(code)
}
}
/**
* Handle a list of construct.
*
* @param {Array<Construct>} list
* @returns {State}
*/
function handleListOfConstructs(list) {
listOfConstructs = list
constructIndex = 0
if (list.length === 0) {
return bogusState
}
return handleConstruct(list[constructIndex])
}
/**
* Handle a single construct.
*
* @param {Construct} construct
* @returns {State}
*/
function handleConstruct(construct) {
return start
/** @type {State} */
function start(code) {
// To do: not needed to store if there is no bogus state, probably?
// Currently doesnt work because `inspect` in document does a check
// w/o a bogus, which doesnt make sense. But it does seem to help perf
// by not storing.
info = store()
currentConstruct = construct
if (!construct.partial) {
context.currentConstruct = construct
}
if (
construct.name &&
context.parser.constructs.disable.null.includes(construct.name)
) {
return nok(code)
}
return construct.tokenize.call(
// If we do have fields, create an object w/ `context` as its
// prototype.
// This allows a “live binding”, which is needed for `interrupt`.
fields ? Object.assign(Object.create(context), fields) : context,
effects,
ok,
nok
)(code)
}
}
/** @type {State} */
function ok(code) {
consumed = true
onreturn(currentConstruct, info)
return returnState
}
/** @type {State} */
function nok(code) {
consumed = true
info.restore()
if (++constructIndex < listOfConstructs.length) {
return handleConstruct(listOfConstructs[constructIndex])
}
return bogusState
}
}
}
/**
* @param {Construct} construct
* @param {number} from
* @returns {void}
*/
function addResult(construct, from) {
if (construct.resolveAll && !resolveAllConstructs.includes(construct)) {
resolveAllConstructs.push(construct)
}
if (construct.resolve) {
splice(
context.events,
from,
context.events.length - from,
construct.resolve(context.events.slice(from), context)
)
}
if (construct.resolveTo) {
context.events = construct.resolveTo(context.events, context)
}
}
/**
* Store state.
*
* @returns {Info}
*/
function store() {
const startPoint = now()
const startPrevious = context.previous
const startCurrentConstruct = context.currentConstruct
const startEventsIndex = context.events.length
const startStack = Array.from(stack)
return {
restore,
from: startEventsIndex
}
/**
* Restore state.
*
* @returns {void}
*/
function restore() {
point = startPoint
context.previous = startPrevious
context.currentConstruct = startCurrentConstruct
context.events.length = startEventsIndex
stack = startStack
accountForPotentialSkip()
}
}
/**
* Move the current point a bit forward in the line when its on a column
* skip.
*
* @returns {void}
*/
function accountForPotentialSkip() {
if (point.line in columnStart && point.column < 2) {
point.column = columnStart[point.line]
point.offset += columnStart[point.line] - 1
}
}
}
/**
* Get the chunks from a slice of chunks in the range of a token.
*
* @param {Array<Chunk>} chunks
* @param {Pick<Token, 'start'|'end'>} token
* @returns {Array<Chunk>}
*/
function sliceChunks(chunks, token) {
const startIndex = token.start._index
const startBufferIndex = token.start._bufferIndex
const endIndex = token.end._index
const endBufferIndex = token.end._bufferIndex
/** @type {Array<Chunk>} */
let view
if (startIndex === endIndex) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view = [chunks[startIndex].slice(startBufferIndex, endBufferIndex)]
} else {
view = chunks.slice(startIndex, endIndex)
if (startBufferIndex > -1) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view[0] = view[0].slice(startBufferIndex)
}
if (endBufferIndex > 0) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view.push(chunks[endIndex].slice(0, endBufferIndex))
}
}
return view
}
/**
* Get the string value of a slice of chunks.
*
* @param {Array<Chunk>} chunks
* @param {boolean} [expandTabs=false]
* @returns {string}
*/
function serializeChunks(chunks, expandTabs) {
let index = -1
/** @type {Array<string>} */
const result = []
/** @type {boolean|undefined} */
let atTab
while (++index < chunks.length) {
const chunk = chunks[index]
/** @type {string} */
let value
if (typeof chunk === 'string') {
value = chunk
} else
switch (chunk) {
case -5: {
value = '\r'
break
}
case -4: {
value = '\n'
break
}
case -3: {
value = '\r' + '\n'
break
}
case -2: {
value = expandTabs ? ' ' : '\t'
break
}
case -1: {
if (!expandTabs && atTab) continue
value = ' '
break
}
default: {
// Currently only replacement character.
value = String.fromCharCode(chunk)
}
}
atTab = chunk === -2
result.push(value)
}
return result.join('')
}

View File

@@ -0,0 +1,6 @@
/** @type {InitialConstruct} */
export const content: InitialConstruct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type Initializer = import('micromark-util-types').Initializer
export type Token = import('micromark-util-types').Token
export type State = import('micromark-util-types').State

View File

@@ -0,0 +1,79 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').State} State
*/
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
/** @type {InitialConstruct} */
export const content = {
tokenize: initializeContent
}
/** @type {Initializer} */
function initializeContent(effects) {
const contentStart = effects.attempt(
this.parser.constructs.contentInitial,
afterContentStartConstruct,
paragraphInitial
)
/** @type {Token} */
let previous
return contentStart
/** @type {State} */
function afterContentStartConstruct(code) {
if (code === null) {
effects.consume(code)
return
}
effects.enter('lineEnding')
effects.consume(code)
effects.exit('lineEnding')
return factorySpace(effects, contentStart, 'linePrefix')
}
/** @type {State} */
function paragraphInitial(code) {
effects.enter('paragraph')
return lineStart(code)
}
/** @type {State} */
function lineStart(code) {
const token = effects.enter('chunkText', {
contentType: 'text',
previous
})
if (previous) {
previous.next = token
}
previous = token
return data(code)
}
/** @type {State} */
function data(code) {
if (code === null) {
effects.exit('chunkText')
effects.exit('paragraph')
effects.consume(code)
return
}
if (markdownLineEnding(code)) {
effects.consume(code)
effects.exit('chunkText')
return lineStart
} // Data.
effects.consume(code)
return data
}
}

View File

@@ -0,0 +1,12 @@
/** @type {InitialConstruct} */
export const document: InitialConstruct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type Initializer = import('micromark-util-types').Initializer
export type Construct = import('micromark-util-types').Construct
export type TokenizeContext = import('micromark-util-types').TokenizeContext
export type Tokenizer = import('micromark-util-types').Tokenizer
export type Token = import('micromark-util-types').Token
export type State = import('micromark-util-types').State
export type Point = import('micromark-util-types').Point
export type StackState = Record<string, unknown>
export type StackItem = [Construct, StackState]

View File

@@ -0,0 +1,373 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Point} Point
*/
/**
* @typedef {Record<string, unknown>} StackState
* @typedef {[Construct, StackState]} StackItem
*/
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {splice} from 'micromark-util-chunked'
/** @type {InitialConstruct} */
export const document = {
tokenize: initializeDocument
}
/** @type {Construct} */
const containerConstruct = {
tokenize: tokenizeContainer
}
/** @type {Initializer} */
function initializeDocument(effects) {
const self = this
/** @type {Array<StackItem>} */
const stack = []
let continued = 0
/** @type {TokenizeContext|undefined} */
let childFlow
/** @type {Token|undefined} */
let childToken
/** @type {number} */
let lineStartOffset
return start
/** @type {State} */
function start(code) {
// First we iterate through the open blocks, starting with the root
// document, and descending through last children down to the last open
// block.
// Each block imposes a condition that the line must satisfy if the block is
// to remain open.
// For example, a block quote requires a `>` character.
// A paragraph requires a non-blank line.
// In this phase we may match all or just some of the open blocks.
// But we cannot close unmatched blocks yet, because we may have a lazy
// continuation line.
if (continued < stack.length) {
const item = stack[continued]
self.containerState = item[1]
return effects.attempt(
item[0].continuation,
documentContinue,
checkNewContainers
)(code)
} // Done.
return checkNewContainers(code)
}
/** @type {State} */
function documentContinue(code) {
continued++ // Note: this field is called `_closeFlow` but it also closes containers.
// Perhaps a good idea to rename it but its already used in the wild by
// extensions.
if (self.containerState._closeFlow) {
self.containerState._closeFlow = undefined
if (childFlow) {
closeFlow()
} // Note: this algorithm for moving events around is similar to the
// algorithm when dealing with lazy lines in `writeToChild`.
const indexBeforeExits = self.events.length
let indexBeforeFlow = indexBeforeExits
/** @type {Point|undefined} */
let point // Find the flow chunk.
while (indexBeforeFlow--) {
if (
self.events[indexBeforeFlow][0] === 'exit' &&
self.events[indexBeforeFlow][1].type === 'chunkFlow'
) {
point = self.events[indexBeforeFlow][1].end
break
}
}
exitContainers(continued) // Fix positions.
let index = indexBeforeExits
while (index < self.events.length) {
self.events[index][1].end = Object.assign({}, point)
index++
} // Inject the exits earlier (theyre still also at the end).
splice(
self.events,
indexBeforeFlow + 1,
0,
self.events.slice(indexBeforeExits)
) // Discard the duplicate exits.
self.events.length = index
return checkNewContainers(code)
}
return start(code)
}
/** @type {State} */
function checkNewContainers(code) {
// Next, after consuming the continuation markers for existing blocks, we
// look for new block starts (e.g. `>` for a block quote).
// If we encounter a new block start, we close any blocks unmatched in
// step 1 before creating the new block as a child of the last matched
// block.
if (continued === stack.length) {
// No need to `check` whether theres a container, of `exitContainers`
// would be moot.
// We can instead immediately `attempt` to parse one.
if (!childFlow) {
return documentContinued(code)
} // If we have concrete content, such as block HTML or fenced code,
// we cant have containers “pierce” into them, so we can immediately
// start.
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) {
return flowStart(code)
} // If we do have flow, it could still be a blank line,
// but wed be interrupting it w/ a new container if theres a current
// construct.
self.interrupt = Boolean(
childFlow.currentConstruct && !childFlow._gfmTableDynamicInterruptHack
)
} // Check if there is a new container.
self.containerState = {}
return effects.check(
containerConstruct,
thereIsANewContainer,
thereIsNoNewContainer
)(code)
}
/** @type {State} */
function thereIsANewContainer(code) {
if (childFlow) closeFlow()
exitContainers(continued)
return documentContinued(code)
}
/** @type {State} */
function thereIsNoNewContainer(code) {
self.parser.lazy[self.now().line] = continued !== stack.length
lineStartOffset = self.now().offset
return flowStart(code)
}
/** @type {State} */
function documentContinued(code) {
// Try new containers.
self.containerState = {}
return effects.attempt(
containerConstruct,
containerContinue,
flowStart
)(code)
}
/** @type {State} */
function containerContinue(code) {
continued++
stack.push([self.currentConstruct, self.containerState]) // Try another.
return documentContinued(code)
}
/** @type {State} */
function flowStart(code) {
if (code === null) {
if (childFlow) closeFlow()
exitContainers(0)
effects.consume(code)
return
}
childFlow = childFlow || self.parser.flow(self.now())
effects.enter('chunkFlow', {
contentType: 'flow',
previous: childToken,
_tokenizer: childFlow
})
return flowContinue(code)
}
/** @type {State} */
function flowContinue(code) {
if (code === null) {
writeToChild(effects.exit('chunkFlow'), true)
exitContainers(0)
effects.consume(code)
return
}
if (markdownLineEnding(code)) {
effects.consume(code)
writeToChild(effects.exit('chunkFlow')) // Get ready for the next line.
continued = 0
self.interrupt = undefined
return start
}
effects.consume(code)
return flowContinue
}
/**
* @param {Token} token
* @param {boolean} [eof]
* @returns {void}
*/
function writeToChild(token, eof) {
const stream = self.sliceStream(token)
if (eof) stream.push(null)
token.previous = childToken
if (childToken) childToken.next = token
childToken = token
childFlow.defineSkip(token.start)
childFlow.write(stream) // Alright, so we just added a lazy line:
//
// ```markdown
// > a
// b.
//
// Or:
//
// > ~~~c
// d
//
// Or:
//
// > | e |
// f
// ```
//
// The construct in the second example (fenced code) does not accept lazy
// lines, so it marked itself as done at the end of its first line, and
// then the content construct parses `d`.
// Most constructs in markdown match on the first line: if the first line
// forms a construct, a non-lazy line cant “unmake” it.
//
// The construct in the third example is potentially a GFM table, and
// those are *weird*.
// It *could* be a table, from the first line, if the following line
// matches a condition.
// In this case, that second line is lazy, which “unmakes” the first line
// and turns the whole into one content block.
//
// Weve now parsed the non-lazy and the lazy line, and can figure out
// whether the lazy line started a new flow block.
// If it did, we exit the current containers between the two flow blocks.
if (self.parser.lazy[token.start.line]) {
let index = childFlow.events.length
while (index--) {
if (
// The token starts before the line ending…
childFlow.events[index][1].start.offset < lineStartOffset && // …and either is not ended yet…
(!childFlow.events[index][1].end || // …or ends after it.
childFlow.events[index][1].end.offset > lineStartOffset)
) {
// Exit: theres still something open, which means its a lazy line
// part of something.
return
}
} // Note: this algorithm for moving events around is similar to the
// algorithm when closing flow in `documentContinue`.
const indexBeforeExits = self.events.length
let indexBeforeFlow = indexBeforeExits
/** @type {boolean|undefined} */
let seen
/** @type {Point|undefined} */
let point // Find the previous chunk (the one before the lazy line).
while (indexBeforeFlow--) {
if (
self.events[indexBeforeFlow][0] === 'exit' &&
self.events[indexBeforeFlow][1].type === 'chunkFlow'
) {
if (seen) {
point = self.events[indexBeforeFlow][1].end
break
}
seen = true
}
}
exitContainers(continued) // Fix positions.
index = indexBeforeExits
while (index < self.events.length) {
self.events[index][1].end = Object.assign({}, point)
index++
} // Inject the exits earlier (theyre still also at the end).
splice(
self.events,
indexBeforeFlow + 1,
0,
self.events.slice(indexBeforeExits)
) // Discard the duplicate exits.
self.events.length = index
}
}
/**
* @param {number} size
* @returns {void}
*/
function exitContainers(size) {
let index = stack.length // Exit open containers.
while (index-- > size) {
const entry = stack[index]
self.containerState = entry[1]
entry[0].exit.call(self, effects)
}
stack.length = size
}
function closeFlow() {
childFlow.write([null])
childToken = undefined
childFlow = undefined
self.containerState._closeFlow = undefined
}
}
/** @type {Tokenizer} */
function tokenizeContainer(effects, ok, nok) {
return factorySpace(
effects,
effects.attempt(this.parser.constructs.document, ok, nok),
'linePrefix',
this.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4
)
}

View File

@@ -0,0 +1,5 @@
/** @type {InitialConstruct} */
export const flow: InitialConstruct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type Initializer = import('micromark-util-types').Initializer
export type State = import('micromark-util-types').State

View File

@@ -0,0 +1,65 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').State} State
*/
import {blankLine, content} from 'micromark-core-commonmark'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
/** @type {InitialConstruct} */
export const flow = {
tokenize: initializeFlow
}
/** @type {Initializer} */
function initializeFlow(effects) {
const self = this
const initial = effects.attempt(
// Try to parse a blank line.
blankLine,
atBlankEnding, // Try to parse initial flow (essentially, only code).
effects.attempt(
this.parser.constructs.flowInitial,
afterConstruct,
factorySpace(
effects,
effects.attempt(
this.parser.constructs.flow,
afterConstruct,
effects.attempt(content, afterConstruct)
),
'linePrefix'
)
)
)
return initial
/** @type {State} */
function atBlankEnding(code) {
if (code === null) {
effects.consume(code)
return
}
effects.enter('lineEndingBlank')
effects.consume(code)
effects.exit('lineEndingBlank')
self.currentConstruct = undefined
return initial
}
/** @type {State} */
function afterConstruct(code) {
if (code === null) {
effects.consume(code)
return
}
effects.enter('lineEnding')
effects.consume(code)
effects.exit('lineEnding')
self.currentConstruct = undefined
return initial
}
}

View File

@@ -0,0 +1,11 @@
export namespace resolver {
const resolveAll: import('micromark-util-types').Resolver
}
export const string: import('micromark-util-types').InitialConstruct
export const text: import('micromark-util-types').InitialConstruct
export type Resolver = import('micromark-util-types').Resolver
export type Initializer = import('micromark-util-types').Initializer
export type Construct = import('micromark-util-types').Construct
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type State = import('micromark-util-types').State
export type Code = import('micromark-util-types').Code

View File

@@ -0,0 +1,218 @@
/**
* @typedef {import('micromark-util-types').Resolver} Resolver
* @typedef {import('micromark-util-types').Initializer} Initializer
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Code} Code
*/
export const resolver = {
resolveAll: createResolver()
}
export const string = initializeFactory('string')
export const text = initializeFactory('text')
/**
* @param {'string'|'text'} field
* @returns {InitialConstruct}
*/
function initializeFactory(field) {
return {
tokenize: initializeText,
resolveAll: createResolver(
field === 'text' ? resolveAllLineSuffixes : undefined
)
}
/** @type {Initializer} */
function initializeText(effects) {
const self = this
const constructs = this.parser.constructs[field]
const text = effects.attempt(constructs, start, notText)
return start
/** @type {State} */
function start(code) {
return atBreak(code) ? text(code) : notText(code)
}
/** @type {State} */
function notText(code) {
if (code === null) {
effects.consume(code)
return
}
effects.enter('data')
effects.consume(code)
return data
}
/** @type {State} */
function data(code) {
if (atBreak(code)) {
effects.exit('data')
return text(code)
} // Data.
effects.consume(code)
return data
}
/**
* @param {Code} code
* @returns {boolean}
*/
function atBreak(code) {
if (code === null) {
return true
}
const list = constructs[code]
let index = -1
if (list) {
while (++index < list.length) {
const item = list[index]
if (!item.previous || item.previous.call(self, self.previous)) {
return true
}
}
}
return false
}
}
}
/**
* @param {Resolver} [extraResolver]
* @returns {Resolver}
*/
function createResolver(extraResolver) {
return resolveAllText
/** @type {Resolver} */
function resolveAllText(events, context) {
let index = -1
/** @type {number|undefined} */
let enter // A rather boring computation (to merge adjacent `data` events) which
// improves mm performance by 29%.
while (++index <= events.length) {
if (enter === undefined) {
if (events[index] && events[index][1].type === 'data') {
enter = index
index++
}
} else if (!events[index] || events[index][1].type !== 'data') {
// Dont do anything if there is one data token.
if (index !== enter + 2) {
events[enter][1].end = events[index - 1][1].end
events.splice(enter + 2, index - enter - 2)
index = enter + 2
}
enter = undefined
}
}
return extraResolver ? extraResolver(events, context) : events
}
}
/**
* A rather ugly set of instructions which again looks at chunks in the input
* stream.
* The reason to do this here is that it is *much* faster to parse in reverse.
* And that we cant hook into `null` to split the line suffix before an EOF.
* To do: figure out if we can make this into a clean utility, or even in core.
* As it will be useful for GFMs literal autolink extension (and maybe even
* tables?)
*
* @type {Resolver}
*/
function resolveAllLineSuffixes(events, context) {
let eventIndex = 0 // Skip first.
while (++eventIndex <= events.length) {
if (
(eventIndex === events.length ||
events[eventIndex][1].type === 'lineEnding') &&
events[eventIndex - 1][1].type === 'data'
) {
const data = events[eventIndex - 1][1]
const chunks = context.sliceStream(data)
let index = chunks.length
let bufferIndex = -1
let size = 0
/** @type {boolean|undefined} */
let tabs
while (index--) {
const chunk = chunks[index]
if (typeof chunk === 'string') {
bufferIndex = chunk.length
while (chunk.charCodeAt(bufferIndex - 1) === 32) {
size++
bufferIndex--
}
if (bufferIndex) break
bufferIndex = -1
} // Number
else if (chunk === -2) {
tabs = true
size++
} else if (chunk === -1) {
// Empty
} else {
// Replacement character, exit.
index++
break
}
}
if (size) {
const token = {
type:
eventIndex === events.length || tabs || size < 2
? 'lineSuffix'
: 'hardBreakTrailing',
start: {
line: data.end.line,
column: data.end.column - size,
offset: data.end.offset - size,
_index: data.start._index + index,
_bufferIndex: index
? bufferIndex
: data.start._bufferIndex + bufferIndex
},
end: Object.assign({}, data.end)
}
data.end = Object.assign({}, token.start)
if (data.start.offset === data.end.offset) {
Object.assign(data, token)
} else {
events.splice(
eventIndex,
0,
['enter', token, context],
['exit', token, context]
)
eventIndex += 2
}
}
eventIndex++
}
}
return events
}

13
vendor/spatie/ignition/node_modules/micromark/lib/parse.d.ts generated vendored Executable file
View File

@@ -0,0 +1,13 @@
/**
* @param {ParseOptions} [options]
* @returns {ParseContext}
*/
export function parse(
options?: import('micromark-util-types').ParseOptions | undefined
): ParseContext
export type InitialConstruct = import('micromark-util-types').InitialConstruct
export type FullNormalizedExtension =
import('micromark-util-types').FullNormalizedExtension
export type ParseOptions = import('micromark-util-types').ParseOptions
export type ParseContext = import('micromark-util-types').ParseContext
export type Create = import('micromark-util-types').Create

52
vendor/spatie/ignition/node_modules/micromark/lib/parse.js generated vendored Executable file
View File

@@ -0,0 +1,52 @@
/**
* @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
* @typedef {import('micromark-util-types').FullNormalizedExtension} FullNormalizedExtension
* @typedef {import('micromark-util-types').ParseOptions} ParseOptions
* @typedef {import('micromark-util-types').ParseContext} ParseContext
* @typedef {import('micromark-util-types').Create} Create
*/
import {combineExtensions} from 'micromark-util-combine-extensions'
import {content} from './initialize/content.js'
import {document} from './initialize/document.js'
import {flow} from './initialize/flow.js'
import {text, string} from './initialize/text.js'
import {createTokenizer} from './create-tokenizer.js'
import * as defaultConstructs from './constructs.js'
/**
* @param {ParseOptions} [options]
* @returns {ParseContext}
*/
export function parse(options = {}) {
/** @type {FullNormalizedExtension} */
// @ts-expect-error `defaultConstructs` is full, so the result will be too.
const constructs = combineExtensions(
// @ts-expect-error Same as above.
[defaultConstructs].concat(options.extensions || [])
)
/** @type {ParseContext} */
const parser = {
defined: [],
lazy: {},
constructs,
content: create(content),
document: create(document),
flow: create(flow),
string: create(string),
text: create(text)
}
return parser
/**
* @param {InitialConstruct} initial
*/
function create(initial) {
return creator
/** @type {Create} */
function creator(from) {
return createTokenizer(parser, initial, from)
}
}
}

View File

@@ -0,0 +1,8 @@
/**
* @param {Array<Event>} events
* @returns {Array<Event>}
*/
export function postprocess(
events: Array<import('micromark-util-types').Event>
): Array<import('micromark-util-types').Event>
export type Event = import('micromark-util-types').Event

View File

@@ -0,0 +1,16 @@
/**
* @typedef {import('micromark-util-types').Event} Event
*/
import {subtokenize} from 'micromark-util-subtokenize'
/**
* @param {Array<Event>} events
* @returns {Array<Event>}
*/
export function postprocess(events) {
while (!subtokenize(events)) {
// Empty
}
return events
}

View File

@@ -0,0 +1,13 @@
/**
* @returns {Preprocessor}
*/
export function preprocess(): Preprocessor
export type Encoding = import('micromark-util-types').Encoding
export type Value = import('micromark-util-types').Value
export type Chunk = import('micromark-util-types').Chunk
export type Code = import('micromark-util-types').Code
export type Preprocessor = (
value: Value,
encoding?: import('micromark-util-types').Encoding | undefined,
end?: boolean | undefined
) => Array<Chunk>

View File

@@ -0,0 +1,129 @@
/**
* @typedef {import('micromark-util-types').Encoding} Encoding
* @typedef {import('micromark-util-types').Value} Value
* @typedef {import('micromark-util-types').Chunk} Chunk
* @typedef {import('micromark-util-types').Code} Code
*/
/**
* @callback Preprocessor
* @param {Value} value
* @param {Encoding} [encoding]
* @param {boolean} [end=false]
* @returns {Array<Chunk>}
*/
const search = /[\0\t\n\r]/g
/**
* @returns {Preprocessor}
*/
export function preprocess() {
let column = 1
let buffer = ''
/** @type {boolean|undefined} */
let start = true
/** @type {boolean|undefined} */
let atCarriageReturn
return preprocessor
/** @type {Preprocessor} */
function preprocessor(value, encoding, end) {
/** @type {Array<Chunk>} */
const chunks = []
/** @type {RegExpMatchArray|null} */
let match
/** @type {number} */
let next
/** @type {number} */
let startPosition
/** @type {number} */
let endPosition
/** @type {Code} */
let code // @ts-expect-error `Buffer` does allow an encoding.
value = buffer + value.toString(encoding)
startPosition = 0
buffer = ''
if (start) {
if (value.charCodeAt(0) === 65279) {
startPosition++
}
start = undefined
}
while (startPosition < value.length) {
search.lastIndex = startPosition
match = search.exec(value)
endPosition =
match && match.index !== undefined ? match.index : value.length
code = value.charCodeAt(endPosition)
if (!match) {
buffer = value.slice(startPosition)
break
}
if (code === 10 && startPosition === endPosition && atCarriageReturn) {
chunks.push(-3)
atCarriageReturn = undefined
} else {
if (atCarriageReturn) {
chunks.push(-5)
atCarriageReturn = undefined
}
if (startPosition < endPosition) {
chunks.push(value.slice(startPosition, endPosition))
column += endPosition - startPosition
}
switch (code) {
case 0: {
chunks.push(65533)
column++
break
}
case 9: {
next = Math.ceil(column / 4) * 4
chunks.push(-2)
while (column++ < next) chunks.push(-1)
break
}
case 10: {
chunks.push(-4)
column = 1
break
}
default: {
atCarriageReturn = true
column = 1
}
}
}
startPosition = endPosition + 1
}
if (end) {
if (atCarriageReturn) chunks.push(-5)
if (buffer) chunks.push(buffer)
chunks.push(null)
}
return chunks
}
}

View File

@@ -0,0 +1 @@
../../../uvu/bin.js

View File

@@ -0,0 +1,20 @@
(The MIT License)
Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2018-2021 Josh Junon
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the 'Software'), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,481 @@
# debug
[![Build Status](https://travis-ci.org/debug-js/debug.svg?branch=master)](https://travis-ci.org/debug-js/debug) [![Coverage Status](https://coveralls.io/repos/github/debug-js/debug/badge.svg?branch=master)](https://coveralls.io/github/debug-js/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors)
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
A tiny JavaScript debugging utility modelled after Node.js core's debugging
technique. Works in Node.js and web browsers.
## Installation
```bash
$ npm install debug
```
## Usage
`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole.
Example [_app.js_](./examples/node/app.js):
```js
var debug = require('debug')('http')
, http = require('http')
, name = 'My App';
// fake app
debug('booting %o', name);
http.createServer(function(req, res){
debug(req.method + ' ' + req.url);
res.end('hello\n');
}).listen(3000, function(){
debug('listening');
});
// fake worker of some kind
require('./worker');
```
Example [_worker.js_](./examples/node/worker.js):
```js
var a = require('debug')('worker:a')
, b = require('debug')('worker:b');
function work() {
a('doing lots of uninteresting work');
setTimeout(work, Math.random() * 1000);
}
work();
function workb() {
b('doing some work');
setTimeout(workb, Math.random() * 2000);
}
workb();
```
The `DEBUG` environment variable is then used to enable these based on space or
comma-delimited names.
Here are some examples:
<img width="647" alt="screen shot 2017-08-08 at 12 53 04 pm" src="https://user-images.githubusercontent.com/71256/29091703-a6302cdc-7c38-11e7-8304-7c0b3bc600cd.png">
<img width="647" alt="screen shot 2017-08-08 at 12 53 38 pm" src="https://user-images.githubusercontent.com/71256/29091700-a62a6888-7c38-11e7-800b-db911291ca2b.png">
<img width="647" alt="screen shot 2017-08-08 at 12 53 25 pm" src="https://user-images.githubusercontent.com/71256/29091701-a62ea114-7c38-11e7-826a-2692bedca740.png">
#### Windows command prompt notes
##### CMD
On Windows the environment variable is set using the `set` command.
```cmd
set DEBUG=*,-not_this
```
Example:
```cmd
set DEBUG=* & node app.js
```
##### PowerShell (VS Code default)
PowerShell uses different syntax to set environment variables.
```cmd
$env:DEBUG = "*,-not_this"
```
Example:
```cmd
$env:DEBUG='app';node app.js
```
Then, run the program to be debugged as usual.
npm script example:
```js
"windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js",
```
## Namespace Colors
Every debug instance has a color generated for it based on its namespace name.
This helps when visually parsing the debug output to identify which debug instance
a debug line belongs to.
#### Node.js
In Node.js, colors are enabled when stderr is a TTY. You also _should_ install
the [`supports-color`](https://npmjs.org/supports-color) module alongside debug,
otherwise debug will only use a small handful of basic colors.
<img width="521" src="https://user-images.githubusercontent.com/71256/29092181-47f6a9e6-7c3a-11e7-9a14-1928d8a711cd.png">
#### Web Browser
Colors are also enabled on "Web Inspectors" that understand the `%c` formatting
option. These are WebKit web inspectors, Firefox ([since version
31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/))
and the Firebug plugin for Firefox (any version).
<img width="524" src="https://user-images.githubusercontent.com/71256/29092033-b65f9f2e-7c39-11e7-8e32-f6f0d8e865c1.png">
## Millisecond diff
When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below:
<img width="647" src="https://user-images.githubusercontent.com/71256/29091956-6bd78372-7c39-11e7-8c55-c948396d6edd.png">
## Conventions
If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output.
## Wildcards
The `*` character may be used as a wildcard. Suppose for example your library has
debuggers named "connect:bodyParser", "connect:compress", "connect:session",
instead of listing all three with
`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do
`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
You can also exclude specific debuggers by prefixing them with a "-" character.
For example, `DEBUG=*,-connect:*` would include all debuggers except those
starting with "connect:".
## Environment Variables
When running through Node.js, you can set a few environment variables that will
change the behavior of the debug logging:
| Name | Purpose |
|-----------|-------------------------------------------------|
| `DEBUG` | Enables/disables specific debugging namespaces. |
| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). |
| `DEBUG_COLORS`| Whether or not to use colors in the debug output. |
| `DEBUG_DEPTH` | Object inspection depth. |
| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. |
__Note:__ The environment variables beginning with `DEBUG_` end up being
converted into an Options object that gets used with `%o`/`%O` formatters.
See the Node.js documentation for
[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options)
for the complete list.
## Formatters
Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting.
Below are the officially supported formatters:
| Formatter | Representation |
|-----------|----------------|
| `%O` | Pretty-print an Object on multiple lines. |
| `%o` | Pretty-print an Object all on a single line. |
| `%s` | String. |
| `%d` | Number (both integer and float). |
| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. |
| `%%` | Single percent sign ('%'). This does not consume an argument. |
### Custom formatters
You can add custom formatters by extending the `debug.formatters` object.
For example, if you wanted to add support for rendering a Buffer as hex with
`%h`, you could do something like:
```js
const createDebug = require('debug')
createDebug.formatters.h = (v) => {
return v.toString('hex')
}
// …elsewhere
const debug = createDebug('foo')
debug('this is hex: %h', new Buffer('hello world'))
// foo this is hex: 68656c6c6f20776f726c6421 +0ms
```
## Browser Support
You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify),
or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest),
if you don't want to build it yourself.
Debug's enable state is currently persisted by `localStorage`.
Consider the situation shown below where you have `worker:a` and `worker:b`,
and wish to debug both. You can enable this using `localStorage.debug`:
```js
localStorage.debug = 'worker:*'
```
And then refresh the page.
```js
a = debug('worker:a');
b = debug('worker:b');
setInterval(function(){
a('doing some work');
}, 1000);
setInterval(function(){
b('doing some work');
}, 1200);
```
In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will—by default—only show messages logged by `debug` if the "Verbose" log level is _enabled_.
<img width="647" src="https://user-images.githubusercontent.com/7143133/152083257-29034707-c42c-4959-8add-3cee850e6fcf.png">
## Output streams
By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method:
Example [_stdout.js_](./examples/node/stdout.js):
```js
var debug = require('debug');
var error = debug('app:error');
// by default stderr is used
error('goes to stderr!');
var log = debug('app:log');
// set this namespace to log via console.log
log.log = console.log.bind(console); // don't forget to bind to console!
log('goes to stdout');
error('still goes to stderr!');
// set all output to go via console.info
// overrides all per-namespace log settings
debug.log = console.info.bind(console);
error('now goes to stdout via console.info');
log('still goes to stdout, but via console.info now');
```
## Extend
You can simply extend debugger
```js
const log = require('debug')('auth');
//creates new debug instance with extended namespace
const logSign = log.extend('sign');
const logLogin = log.extend('login');
log('hello'); // auth hello
logSign('hello'); //auth:sign hello
logLogin('hello'); //auth:login hello
```
## Set dynamically
You can also enable debug dynamically by calling the `enable()` method :
```js
let debug = require('debug');
console.log(1, debug.enabled('test'));
debug.enable('test');
console.log(2, debug.enabled('test'));
debug.disable();
console.log(3, debug.enabled('test'));
```
print :
```
1 false
2 true
3 false
```
Usage :
`enable(namespaces)`
`namespaces` can include modes separated by a colon and wildcards.
Note that calling `enable()` completely overrides previously set DEBUG variable :
```
$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))'
=> false
```
`disable()`
Will disable all namespaces. The functions returns the namespaces currently
enabled (and skipped). This can be useful if you want to disable debugging
temporarily without knowing what was enabled to begin with.
For example:
```js
let debug = require('debug');
debug.enable('foo:*,-foo:bar');
let namespaces = debug.disable();
debug.enable(namespaces);
```
Note: There is no guarantee that the string will be identical to the initial
enable string, but semantically they will be identical.
## Checking whether a debug target is enabled
After you've created a debug instance, you can determine whether or not it is
enabled by checking the `enabled` property:
```javascript
const debug = require('debug')('http');
if (debug.enabled) {
// do stuff...
}
```
You can also manually toggle this property to force the debug instance to be
enabled or disabled.
## Usage in child processes
Due to the way `debug` detects if the output is a TTY or not, colors are not shown in child processes when `stderr` is piped. A solution is to pass the `DEBUG_COLORS=1` environment variable to the child process.
For example:
```javascript
worker = fork(WORKER_WRAP_PATH, [workerPath], {
stdio: [
/* stdin: */ 0,
/* stdout: */ 'pipe',
/* stderr: */ 'pipe',
'ipc',
],
env: Object.assign({}, process.env, {
DEBUG_COLORS: 1 // without this settings, colors won't be shown
}),
});
worker.stderr.pipe(process.stderr, { end: false });
```
## Authors
- TJ Holowaychuk
- Nathan Rajlich
- Andrew Rhyne
- Josh Junon
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)]
<a href="https://opencollective.com/debug/backer/0/website" target="_blank"><img src="https://opencollective.com/debug/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/1/website" target="_blank"><img src="https://opencollective.com/debug/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/2/website" target="_blank"><img src="https://opencollective.com/debug/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/3/website" target="_blank"><img src="https://opencollective.com/debug/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/4/website" target="_blank"><img src="https://opencollective.com/debug/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/5/website" target="_blank"><img src="https://opencollective.com/debug/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/6/website" target="_blank"><img src="https://opencollective.com/debug/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/7/website" target="_blank"><img src="https://opencollective.com/debug/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/8/website" target="_blank"><img src="https://opencollective.com/debug/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/9/website" target="_blank"><img src="https://opencollective.com/debug/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/10/website" target="_blank"><img src="https://opencollective.com/debug/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/11/website" target="_blank"><img src="https://opencollective.com/debug/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/12/website" target="_blank"><img src="https://opencollective.com/debug/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/13/website" target="_blank"><img src="https://opencollective.com/debug/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/14/website" target="_blank"><img src="https://opencollective.com/debug/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/15/website" target="_blank"><img src="https://opencollective.com/debug/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/16/website" target="_blank"><img src="https://opencollective.com/debug/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/17/website" target="_blank"><img src="https://opencollective.com/debug/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/18/website" target="_blank"><img src="https://opencollective.com/debug/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/19/website" target="_blank"><img src="https://opencollective.com/debug/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/20/website" target="_blank"><img src="https://opencollective.com/debug/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/21/website" target="_blank"><img src="https://opencollective.com/debug/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/22/website" target="_blank"><img src="https://opencollective.com/debug/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/23/website" target="_blank"><img src="https://opencollective.com/debug/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/24/website" target="_blank"><img src="https://opencollective.com/debug/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/25/website" target="_blank"><img src="https://opencollective.com/debug/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/26/website" target="_blank"><img src="https://opencollective.com/debug/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/27/website" target="_blank"><img src="https://opencollective.com/debug/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/28/website" target="_blank"><img src="https://opencollective.com/debug/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/29/website" target="_blank"><img src="https://opencollective.com/debug/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)]
<a href="https://opencollective.com/debug/sponsor/0/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/1/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/2/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/3/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/4/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/5/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/6/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/7/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/8/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/9/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/10/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/11/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/12/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/13/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/14/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/15/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/16/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/17/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/18/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/19/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/20/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/21/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/22/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/23/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/24/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/25/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/26/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/27/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/28/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/29/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/29/avatar.svg"></a>
## License
(The MIT License)
Copyright (c) 2014-2017 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Copyright (c) 2018-2021 Josh Junon
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,269 @@
/* eslint-env browser */
/**
* This is the web browser implementation of `debug()`.
*/
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = localstorage();
exports.destroy = (() => {
let warned = false;
return () => {
if (!warned) {
warned = true;
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
};
})();
/**
* Colors.
*/
exports.colors = [
'#0000CC',
'#0000FF',
'#0033CC',
'#0033FF',
'#0066CC',
'#0066FF',
'#0099CC',
'#0099FF',
'#00CC00',
'#00CC33',
'#00CC66',
'#00CC99',
'#00CCCC',
'#00CCFF',
'#3300CC',
'#3300FF',
'#3333CC',
'#3333FF',
'#3366CC',
'#3366FF',
'#3399CC',
'#3399FF',
'#33CC00',
'#33CC33',
'#33CC66',
'#33CC99',
'#33CCCC',
'#33CCFF',
'#6600CC',
'#6600FF',
'#6633CC',
'#6633FF',
'#66CC00',
'#66CC33',
'#9900CC',
'#9900FF',
'#9933CC',
'#9933FF',
'#99CC00',
'#99CC33',
'#CC0000',
'#CC0033',
'#CC0066',
'#CC0099',
'#CC00CC',
'#CC00FF',
'#CC3300',
'#CC3333',
'#CC3366',
'#CC3399',
'#CC33CC',
'#CC33FF',
'#CC6600',
'#CC6633',
'#CC9900',
'#CC9933',
'#CCCC00',
'#CCCC33',
'#FF0000',
'#FF0033',
'#FF0066',
'#FF0099',
'#FF00CC',
'#FF00FF',
'#FF3300',
'#FF3333',
'#FF3366',
'#FF3399',
'#FF33CC',
'#FF33FF',
'#FF6600',
'#FF6633',
'#FF9900',
'#FF9933',
'#FFCC00',
'#FFCC33'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
// eslint-disable-next-line complexity
function useColors() {
// NB: In an Electron preload script, document will be defined but not fully
// initialized. Since we know we're in Chrome, we'll just detect this case
// explicitly
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
return true;
}
// Internet Explorer and Edge do not support colors.
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false;
}
// Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
// Is firebug? http://stackoverflow.com/a/398120/376773
(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
// Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
// Double check webkit in userAgent just in case we are in a worker
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs(args) {
args[0] = (this.useColors ? '%c' : '') +
this.namespace +
(this.useColors ? ' %c' : ' ') +
args[0] +
(this.useColors ? '%c ' : ' ') +
'+' + module.exports.humanize(this.diff);
if (!this.useColors) {
return;
}
const c = 'color: ' + this.color;
args.splice(1, 0, c, 'color: inherit');
// The final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
let index = 0;
let lastC = 0;
args[0].replace(/%[a-zA-Z%]/g, match => {
if (match === '%%') {
return;
}
index++;
if (match === '%c') {
// We only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
}
/**
* Invokes `console.debug()` when available.
* No-op when `console.debug` is not a "function".
* If `console.debug` is not available, falls back
* to `console.log`.
*
* @api public
*/
exports.log = console.debug || console.log || (() => {});
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (namespaces) {
exports.storage.setItem('debug', namespaces);
} else {
exports.storage.removeItem('debug');
}
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
let r;
try {
r = exports.storage.getItem('debug');
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
if (!r && typeof process !== 'undefined' && 'env' in process) {
r = process.env.DEBUG;
}
return r;
}
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage() {
try {
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
// The Browser also has localStorage in the global context.
return localStorage;
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
formatters.j = function (v) {
try {
return JSON.stringify(v);
} catch (error) {
return '[UnexpectedJSONParseError]: ' + error.message;
}
};

View File

@@ -0,0 +1,274 @@
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*/
function setup(env) {
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = require('ms');
createDebug.destroy = destroy;
Object.keys(env).forEach(key => {
createDebug[key] = env[key];
});
/**
* The currently active debug mode names, and names to skip.
*/
createDebug.names = [];
createDebug.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
*/
createDebug.formatters = {};
/**
* Selects a color for a debug namespace
* @param {String} namespace The namespace string for the debug instance to be colored
* @return {Number|String} An ANSI color code for the given namespace
* @api private
*/
function selectColor(namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
}
createDebug.selectColor = selectColor;
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
let prevTime;
let enableOverride = null;
let namespacesCache;
let enabledCache;
function debug(...args) {
// Disabled?
if (!debug.enabled) {
return;
}
const self = debug;
// Set `diff` timestamp
const curr = Number(new Date());
const ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
args[0] = createDebug.coerce(args[0]);
if (typeof args[0] !== 'string') {
// Anything else let's inspect with %O
args.unshift('%O');
}
// Apply any `formatters` transformations
let index = 0;
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
// If we encounter an escaped % then don't increase the array index
if (match === '%%') {
return '%';
}
index++;
const formatter = createDebug.formatters[format];
if (typeof formatter === 'function') {
const val = args[index];
match = formatter.call(self, val);
// Now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
// Apply env-specific formatting (colors, etc.)
createDebug.formatArgs.call(self, args);
const logFn = self.log || createDebug.log;
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.useColors = createDebug.useColors();
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
Object.defineProperty(debug, 'enabled', {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride !== null) {
return enableOverride;
}
if (namespacesCache !== createDebug.namespaces) {
namespacesCache = createDebug.namespaces;
enabledCache = createDebug.enabled(namespace);
}
return enabledCache;
},
set: v => {
enableOverride = v;
}
});
// Env-specific initialization logic for debug instances
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
return debug;
}
function extend(namespace, delimiter) {
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
return newDebug;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
createDebug.save(namespaces);
createDebug.namespaces = namespaces;
createDebug.names = [];
createDebug.skips = [];
let i;
const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
const len = split.length;
for (i = 0; i < len; i++) {
if (!split[i]) {
// ignore empty strings
continue;
}
namespaces = split[i].replace(/\*/g, '.*?');
if (namespaces[0] === '-') {
createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$'));
} else {
createDebug.names.push(new RegExp('^' + namespaces + '$'));
}
}
}
/**
* Disable debug output.
*
* @return {String} namespaces
* @api public
*/
function disable() {
const namespaces = [
...createDebug.names.map(toNamespace),
...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
].join(',');
createDebug.enable('');
return namespaces;
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
if (name[name.length - 1] === '*') {
return true;
}
let i;
let len;
for (i = 0, len = createDebug.skips.length; i < len; i++) {
if (createDebug.skips[i].test(name)) {
return false;
}
}
for (i = 0, len = createDebug.names.length; i < len; i++) {
if (createDebug.names[i].test(name)) {
return true;
}
}
return false;
}
/**
* Convert regexp to namespace
*
* @param {RegExp} regxep
* @return {String} namespace
* @api private
*/
function toNamespace(regexp) {
return regexp.toString()
.substring(2, regexp.toString().length - 2)
.replace(/\.\*\?$/, '*');
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) {
return val.stack || val.message;
}
return val;
}
/**
* XXX DO NOT USE. This is a temporary stub function.
* XXX It WILL be removed in the next major release.
*/
function destroy() {
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
createDebug.enable(createDebug.load());
return createDebug;
}
module.exports = setup;

View File

@@ -0,0 +1,10 @@
/**
* Detect Electron renderer / nwjs process, which is node, but we should
* treat as a browser.
*/
if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
module.exports = require('./browser.js');
} else {
module.exports = require('./node.js');
}

View File

@@ -0,0 +1,263 @@
/**
* Module dependencies.
*/
const tty = require('tty');
const util = require('util');
/**
* This is the Node.js implementation of `debug()`.
*/
exports.init = init;
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.destroy = util.deprecate(
() => {},
'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
);
/**
* Colors.
*/
exports.colors = [6, 2, 3, 4, 5, 1];
try {
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
// eslint-disable-next-line import/no-extraneous-dependencies
const supportsColor = require('supports-color');
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
exports.colors = [
20,
21,
26,
27,
32,
33,
38,
39,
40,
41,
42,
43,
44,
45,
56,
57,
62,
63,
68,
69,
74,
75,
76,
77,
78,
79,
80,
81,
92,
93,
98,
99,
112,
113,
128,
129,
134,
135,
148,
149,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
178,
179,
184,
185,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
214,
215,
220,
221
];
}
} catch (error) {
// Swallow - we only care if `supports-color` is available; it doesn't have to be.
}
/**
* Build up the default `inspectOpts` object from the environment variables.
*
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
*/
exports.inspectOpts = Object.keys(process.env).filter(key => {
return /^debug_/i.test(key);
}).reduce((obj, key) => {
// Camel-case
const prop = key
.substring(6)
.toLowerCase()
.replace(/_([a-z])/g, (_, k) => {
return k.toUpperCase();
});
// Coerce string value into JS value
let val = process.env[key];
if (/^(yes|on|true|enabled)$/i.test(val)) {
val = true;
} else if (/^(no|off|false|disabled)$/i.test(val)) {
val = false;
} else if (val === 'null') {
val = null;
} else {
val = Number(val);
}
obj[prop] = val;
return obj;
}, {});
/**
* Is stdout a TTY? Colored output is enabled when `true`.
*/
function useColors() {
return 'colors' in exports.inspectOpts ?
Boolean(exports.inspectOpts.colors) :
tty.isatty(process.stderr.fd);
}
/**
* Adds ANSI color escape codes if enabled.
*
* @api public
*/
function formatArgs(args) {
const {namespace: name, useColors} = this;
if (useColors) {
const c = this.color;
const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
args[0] = prefix + args[0].split('\n').join('\n' + prefix);
args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
} else {
args[0] = getDate() + name + ' ' + args[0];
}
}
function getDate() {
if (exports.inspectOpts.hideDate) {
return '';
}
return new Date().toISOString() + ' ';
}
/**
* Invokes `util.format()` with the specified arguments and writes to stderr.
*/
function log(...args) {
return process.stderr.write(util.format(...args) + '\n');
}
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
if (namespaces) {
process.env.DEBUG = namespaces;
} else {
// If you set a process.env field to null or undefined, it gets cast to the
// string 'null' or 'undefined'. Just delete instead.
delete process.env.DEBUG;
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
return process.env.DEBUG;
}
/**
* Init logic for `debug` instances.
*
* Create a new `inspectOpts` object in case `useColors` is set
* differently for a particular `debug` instance.
*/
function init(debug) {
debug.inspectOpts = {};
const keys = Object.keys(exports.inspectOpts);
for (let i = 0; i < keys.length; i++) {
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %o to `util.inspect()`, all on a single line.
*/
formatters.o = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts)
.split('\n')
.map(str => str.trim())
.join(' ');
};
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
formatters.O = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts);
};

1751
vendor/spatie/ignition/node_modules/micromark/readme.md generated vendored Executable file

File diff suppressed because it is too large Load Diff

22
vendor/spatie/ignition/node_modules/micromark/stream.d.ts generated vendored Executable file
View File

@@ -0,0 +1,22 @@
/**
* @param {Options} [options]
* @returns {MinimalDuplex}
*/
export function stream(
options?: import('micromark-util-types').Options | undefined
): MinimalDuplex
export type Options = import('micromark-util-types').Options
export type Value = import('micromark-util-types').Value
export type Encoding = import('micromark-util-types').Encoding
export type Callback = (error?: Error) => void
export type MinimalDuplex = Omit<
NodeJS.ReadableStream & NodeJS.WritableStream,
| 'read'
| 'setEncoding'
| 'pause'
| 'resume'
| 'isPaused'
| 'unpipe'
| 'unshift'
| 'wrap'
>

196
vendor/spatie/ignition/node_modules/micromark/stream.js generated vendored Executable file
View File

@@ -0,0 +1,196 @@
/**
* @typedef {import('micromark-util-types').Options} Options
* @typedef {import('micromark-util-types').Value} Value
* @typedef {import('micromark-util-types').Encoding} Encoding
*/
/**
* @typedef {((error?: Error) => void)} Callback
* @typedef {Omit<NodeJS.ReadableStream & NodeJS.WritableStream, 'read'|'setEncoding'|'pause'|'resume'|'isPaused'|'unpipe'|'unshift'|'wrap'>} MinimalDuplex
*/
import {EventEmitter} from 'events'
import {compile} from './lib/compile.js'
import {parse} from './lib/parse.js'
import {postprocess} from './lib/postprocess.js'
import {preprocess} from './lib/preprocess.js'
/**
* @param {Options} [options]
* @returns {MinimalDuplex}
*/
export function stream(options) {
const prep = preprocess()
const tokenize = parse(options).document().write
const comp = compile(options)
/** @type {boolean} */
let ended
/**
* Write a chunk into memory.
*
* @param {Value} chunk
* @param {Encoding} encoding
* @param {Callback} callback
*/
const write =
/**
* @type {(
* ((value?: Value, encoding?: Encoding, callback?: Callback) => boolean) &
* ((value: Value, callback?: Callback) => boolean)
* )}
*/
/**
* @param {Value} [chunk]
* @param {Encoding} [encoding]
* @param {Callback} [callback]
*/
function (chunk, encoding, callback) {
if (typeof encoding === 'function') {
callback = encoding
encoding = undefined
}
if (ended) {
throw new Error('Did not expect `write` after `end`')
}
tokenize(prep(chunk || '', encoding))
if (callback) {
callback()
} // Signal succesful write.
return true
}
/**
* End the writing.
* Passes all arguments to a final `write`.
*
* @param {Value} chunk
* @param {Encoding} encoding
* @param {Callback} callback
*/
const end =
/**
* @type {(
* ((value?: Value, encoding?: Encoding, callback?: Callback) => boolean) &
* ((value: Value, callback?: Callback) => boolean)
* )}
*/
/**
* @param {Value} [chunk]
* @param {Encoding} [encoding]
* @param {Callback} [callback]
*/
function (chunk, encoding, callback) {
if (typeof chunk === 'function') {
encoding = chunk
chunk = undefined
}
write(chunk, encoding, callback)
emitter.emit(
'data',
comp(postprocess(tokenize(prep('', encoding, true))))
)
emitter.emit('end')
ended = true
return true
}
/** @type {MinimalDuplex} */
// @ts-expect-error `addListener` is fine.
const emitter = Object.assign(new EventEmitter(), {
writable: true,
readable: true,
write,
end,
pipe
})
return emitter
/**
* Pipe the processor into a writable stream.
* Basically `Stream#pipe`, but inlined and simplified to keep the bundled
* size down.
* See: <https://github.com/nodejs/node/blob/43a5170/lib/internal/streams/legacy.js#L13>.
*
* @template {NodeJS.WritableStream} T
* @param {T} dest
* @param {{end?: boolean}} [options]
* @returns {T}
*/
function pipe(dest, options) {
emitter.on('data', ondata)
emitter.on('error', onerror)
emitter.on('end', cleanup)
emitter.on('close', cleanup) // If the `end` option is not supplied, `dest.end()` will be
// called when the `end` or `close` events are received.
// @ts-expect-error `_isStdio` is available on `std{err,out}`
if (!dest._isStdio && (!options || options.end !== false)) {
emitter.on('end', onend)
}
dest.on('error', onerror)
dest.on('close', cleanup)
dest.emit('pipe', emitter)
return dest
/**
* End destination.
*
* @returns {void}
*/
function onend() {
if (dest.end) {
dest.end()
}
}
/**
* Handle data.
*
* @param {string} chunk
* @returns {void}
*/
function ondata(chunk) {
if (dest.writable) {
dest.write(chunk)
}
}
/**
* Clean listeners.
*
* @returns {void}
*/
function cleanup() {
emitter.removeListener('data', ondata)
emitter.removeListener('end', onend)
emitter.removeListener('error', onerror)
emitter.removeListener('end', cleanup)
emitter.removeListener('close', cleanup)
dest.removeListener('error', onerror)
dest.removeListener('close', cleanup)
}
/**
* Close dangling pipes and handle unheard errors.
*
* @param {Error?} [error]
* @returns {void}
*/
function onerror(error) {
cleanup()
if (!emitter.listenerCount('error')) {
throw error // Unhandled stream error in pipe.
}
}
}
}