Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pandoc like behavior for nested container directives #25

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@
*.tsbuildinfo
coverage/
node_modules/
/lib/
/index.js
yarn.lock
!dev/index.d.ts
16 changes: 13 additions & 3 deletions dev/lib/directive-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
? tail[2].sliceSerialize(tail[1], true).length
: 0
let sizeOpen = 0
let lvl = 0
/** @type {Token} */
let previous

Expand Down Expand Up @@ -239,18 +240,27 @@ function tokenizeDirectiveContainer(effects, ok, nok) {
return closingSequence
}

if (size < sizeOpen) return nok(code)
if (size === 0) return nok(code)

effects.exit('directiveContainerSequence')
return factorySpace(effects, closingSequenceEnd, types.whitespace)(code)
}

/** @type {State} */
function closingSequenceEnd(code) {
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('directiveContainerFence')
return ok(code)
if (lvl > 0) {
lvl--
return nok(code)
}

if (size >= sizeOpen) {
effects.exit('directiveContainerFence')
return ok(code)
}
}

lvl++
return nok(code)
}
}
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Note: more types exported from `index.d.ts`.
export { directive } from './lib/syntax.js';
export { directiveHtml } from './lib/html.js';
259 changes: 259 additions & 0 deletions lib/directive-container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/**
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').Token} Token
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
*/

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveContainer = {
tokenize: tokenizeDirectiveContainer,
concrete: true
};
const label = {
tokenize: tokenizeLabel,
partial: true
};
const attributes = {
tokenize: tokenizeAttributes,
partial: true
};
const nonLazyLine = {
tokenize: tokenizeNonLazyLine,
partial: true
};

/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveContainer(effects, ok, nok) {
const self = this;
const tail = self.events[self.events.length - 1];
const initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
let sizeOpen = 0;
let lvl = 0;
/** @type {Token} */
let previous;
return start;

/** @type {State} */
function start(code) {
effects.enter('directiveContainer');
effects.enter('directiveContainerFence');
effects.enter('directiveContainerSequence');
return sequenceOpen(code);
}

/** @type {State} */
function sequenceOpen(code) {
if (code === 58) {
effects.consume(code);
sizeOpen++;
return sequenceOpen;
}
if (sizeOpen < 3) {
return nok(code);
}
effects.exit('directiveContainerSequence');
return factoryName.call(self, effects, afterName, nok, 'directiveContainerName')(code);
}

/** @type {State} */
function afterName(code) {
return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
}

/** @type {State} */
function afterLabel(code) {
return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
}

/** @type {State} */
function afterAttributes(code) {
return factorySpace(effects, openAfter, "whitespace")(code);
}

/** @type {State} */
function openAfter(code) {
effects.exit('directiveContainerFence');
if (code === null) {
return afterOpening(code);
}
if (markdownLineEnding(code)) {
if (self.interrupt) {
return ok(code);
}
return effects.attempt(nonLazyLine, contentStart, afterOpening)(code);
}
return nok(code);
}

/** @type {State} */
function afterOpening(code) {
effects.exit('directiveContainer');
return ok(code);
}

/** @type {State} */
function contentStart(code) {
if (code === null) {
effects.exit('directiveContainer');
return ok(code);
}
effects.enter('directiveContainerContent');
return lineStart(code);
}

/** @type {State} */
function lineStart(code) {
if (code === null) {
return after(code);
}
return effects.attempt({
tokenize: tokenizeClosingFence,
partial: true
}, after, initialSize ? factorySpace(effects, chunkStart, "linePrefix", initialSize + 1) : chunkStart)(code);
}

/** @type {State} */
function chunkStart(code) {
if (code === null) {
return after(code);
}
const token = effects.enter("chunkDocument", {
contentType: "document",
previous
});
if (previous) previous.next = token;
previous = token;
return contentContinue(code);
}

/** @type {State} */
function contentContinue(code) {
if (code === null) {
const t = effects.exit("chunkDocument");
self.parser.lazy[t.start.line] = false;
return after(code);
}
if (markdownLineEnding(code)) {
return effects.check(nonLazyLine, nonLazyLineAfter, lineAfter)(code);
}
effects.consume(code);
return contentContinue;
}

/** @type {State} */
function nonLazyLineAfter(code) {
effects.consume(code);
const t = effects.exit("chunkDocument");
self.parser.lazy[t.start.line] = false;
return lineStart;
}

/** @type {State} */
function lineAfter(code) {
const t = effects.exit("chunkDocument");
self.parser.lazy[t.start.line] = false;
return after(code);
}

/** @type {State} */
function after(code) {
effects.exit('directiveContainerContent');
effects.exit('directiveContainer');
return ok(code);
}

/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeClosingFence(effects, ok, nok) {
let size = 0;
return factorySpace(effects, closingPrefixAfter, "linePrefix", 4);

/** @type {State} */
function closingPrefixAfter(code) {
effects.enter('directiveContainerFence');
effects.enter('directiveContainerSequence');
return closingSequence(code);
}

/** @type {State} */
function closingSequence(code) {
if (code === 58) {
effects.consume(code);
size++;
return closingSequence;
}
if (size === 0) return nok(code);
effects.exit('directiveContainerSequence');
return factorySpace(effects, closingSequenceEnd, "whitespace")(code);
}

/** @type {State} */
function closingSequenceEnd(code) {
if (code === null || markdownLineEnding(code)) {
if (lvl > 0) {
lvl--;
return nok(code);
}
if (size >= sizeOpen) {
effects.exit('directiveContainerFence');
return ok(code);
}
}
lvl++;
return nok(code);
}
}
}

/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(effects, ok, nok, 'directiveContainerLabel', 'directiveContainerLabelMarker', 'directiveContainerLabelString', true);
}

/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(effects, ok, nok, 'directiveContainerAttributes', 'directiveContainerAttributesMarker', 'directiveContainerAttribute', 'directiveContainerAttributeId', 'directiveContainerAttributeClass', 'directiveContainerAttributeName', 'directiveContainerAttributeInitializerMarker', 'directiveContainerAttributeValueLiteral', 'directiveContainerAttributeValue', 'directiveContainerAttributeValueMarker', 'directiveContainerAttributeValueData', true);
}

/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeNonLazyLine(effects, ok, nok) {
const self = this;
return start;

/** @type {State} */
function start(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return lineStart;
}

/** @type {State} */
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
}
}
Loading