-
Notifications
You must be signed in to change notification settings - Fork 61
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
Proposal: markdown-it-htmyst #396
Draft
tavin
wants to merge
1
commit into
jupyter-book:main
Choose a base branch
from
tavin:markdown-it-htmyst
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"name": "markdown-it-htmyst", | ||
"version": "0.1.3", | ||
"sideEffects": false, | ||
"license": "MIT", | ||
"description": "markdown-it HTML hinter for MyST roles and directives", | ||
"author": "Tavin Cole <7685034+tavin@users.noreply.github.com>", | ||
"homepage": "https://github.com/executablebooks/mystjs/tree/main/packages/markdown-it-htmyst", | ||
"main": "./dist/cjs/index.js", | ||
"module": "./dist/esm/index.js", | ||
"types": "./dist/types/index.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"exports": { | ||
".": { | ||
"import": "./dist/esm/index.js", | ||
"require": "./dist/cjs/index.js" | ||
} | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/executablebooks/mystjs.git" | ||
}, | ||
"scripts": { | ||
"clean": "rimraf dist", | ||
"build:esm": "tsc --project ./tsconfig.json --module es2015 --outDir dist/esm", | ||
"build:cjs": "tsc --project ./tsconfig.json --module commonjs --outDir dist/cjs", | ||
"declarations": "tsc --project ./tsconfig.json --declaration --emitDeclarationOnly --declarationMap --outDir dist/types", | ||
"bundle": "esbuild dist/cjs/index.js --bundle --format=cjs --outfile=dist/markdown-it-htmyst.js", | ||
"build": "npm-run-all -l clean -p build:cjs build:esm declarations -s bundle", | ||
"lint": "eslint \"src/**/!(*.spec).ts\" -c ./.eslintrc.js", | ||
"lint:format": "npx prettier --check \"src/**/*.ts\"", | ||
"test": "jest", | ||
"test:watch": "jest --watchAll" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/executablebooks/mystjs/issues" | ||
}, | ||
"dependencies": { | ||
"markdown-it": "^13.0.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import type StateCore from 'markdown-it/lib/rules_core/state_core'; | ||
import type Token from 'markdown-it/lib/token'; | ||
|
||
import { | ||
findTokenPair, | ||
ARG_OPEN, | ||
ARG_CLOSE, | ||
BODY_OPEN, | ||
BODY_CLOSE, | ||
} from './util'; | ||
|
||
const ADMONITIONS = [ | ||
'admonition', | ||
'attention', | ||
'caution', | ||
'danger', | ||
'error', | ||
'hint', | ||
'important', | ||
'note', | ||
'seealso', | ||
'tip', | ||
'warning', | ||
]; | ||
|
||
/** | ||
* Enrich admonition directives with markup tags. | ||
* | ||
* @return true if new tokens are inserted into the array | ||
*/ | ||
export function admonitionDecorator(state: StateCore, tokens: Token[]): boolean { | ||
|
||
let kind = tokens[0].info; | ||
|
||
if (!ADMONITIONS.includes(kind)) return false; | ||
|
||
let ins = false; | ||
let [...arg] = findTokenPair(tokens, [ARG_OPEN, ARG_CLOSE], 1); | ||
|
||
if (arg[1] === undefined) { | ||
const title = new state.Token('inline', '', 0); | ||
title.content = kind.replace(/^./, char => char.toUpperCase()); | ||
title.children = []; | ||
tokens.splice(1, 0, new state.Token(ARG_OPEN, 'p', 1), | ||
title, new state.Token(ARG_CLOSE, 'p', -1)); | ||
arg = [1, 3]; | ||
ins = true; | ||
} | ||
|
||
let [...body] = findTokenPair(tokens, [BODY_OPEN, BODY_CLOSE], 1); | ||
|
||
tokens.at(0).attrSet('class', `admonition ${kind}`); | ||
tokens.at(arg[0]).attrSet('class', 'admonition-title'); | ||
|
||
[0, -1].map(tokens.at, tokens).forEach(token => { | ||
token.hidden = false; | ||
token.block = true; | ||
token.tag = 'aside'; | ||
}); | ||
|
||
arg.map(tokens.at, tokens).forEach(token => { | ||
token.hidden = false; | ||
token.block = true; | ||
token.tag = 'p'; | ||
}); | ||
|
||
body.map(tokens.at, tokens).forEach(token => { | ||
token.hidden = false; | ||
token.block = true; | ||
token.tag = 'div'; | ||
}); | ||
|
||
return ins; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import type MarkdownIt from 'markdown-it/lib'; | ||
import type StateCore from 'markdown-it/lib/rules_core/state_core'; | ||
import type Token from 'markdown-it/lib/token'; | ||
|
||
import { admonitionDecorator } from './admonition'; | ||
import { proofDecorator } from './proof'; | ||
|
||
type Decorator = (state: StateCore, tokens: Token[]) => boolean | void; | ||
|
||
const DEFAULT_DECORATORS = [ | ||
admonitionDecorator, | ||
proofDecorator, | ||
]; | ||
|
||
/** | ||
* Factory to implement the following rule: | ||
* | ||
* - Run through the parsed token array. | ||
* - Find all slices from `parsed_directive_open` to `parsed_directive_close`. | ||
* - Pass the slices through the decorators. | ||
*/ | ||
function rule(decorators: Decorator[] = DEFAULT_DECORATORS) { | ||
|
||
return (state: StateCore) => { | ||
const tokens = state.tokens; | ||
const stack = []; | ||
for (let j = 0; j < tokens.length; ++j) { | ||
if (tokens[j].type === 'parsed_directive_open') { | ||
stack.push(j); | ||
} else if (tokens[j].type === 'parsed_directive_close') { | ||
let i = stack.pop()!; | ||
// in principle we'd like a view of the array from i to j; | ||
// hopefully all modern JS engines like V8 will optimize slice() as COW | ||
const slice = tokens.slice(i, j + 1); | ||
let subst = false; | ||
for (let fn of decorators) { | ||
subst = fn(state, slice) || subst; | ||
} | ||
if (subst) { | ||
tokens.splice(i, j - i + 1, ...slice); | ||
j = i + slice.length - 1; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* A markdown-it plugin for adding markup to parsed myst directives. | ||
*/ | ||
export function htmystPlugin(md: MarkdownIt, ...decorators: Decorator[]) { | ||
md.core.ruler.after('run_directives', 'decorate_directives', | ||
decorators.length ? rule(decorators) : rule()); | ||
} | ||
|
||
export default htmystPlugin; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import type StateCore from 'markdown-it/lib/rules_core/state_core'; | ||
import type Token from 'markdown-it/lib/token'; | ||
|
||
import { | ||
findTokenPair, | ||
ARG_OPEN, | ||
ARG_CLOSE, | ||
BODY_OPEN, | ||
BODY_CLOSE, | ||
} from './util'; | ||
|
||
/** | ||
* Enrich proof directives with markup tags. | ||
* | ||
* @return true if new tokens are inserted into the array | ||
*/ | ||
export function proofDecorator(state: StateCore, tokens: Token[]): boolean { | ||
|
||
let kind = tokens[0].info; | ||
|
||
if (!kind.startsWith('prf:')) return false; | ||
|
||
const title = new state.Token('inline', '', 0); | ||
title.content = kind.replace(/^prf:(.)/, (_, char) => char.toUpperCase()); | ||
title.children = []; | ||
|
||
let [...arg] = findTokenPair(tokens, [ARG_OPEN, ARG_CLOSE], 1); | ||
|
||
if (arg[1] === undefined) { | ||
tokens.splice(1, 0, new state.Token(ARG_OPEN, 'p', 1), | ||
title, new state.Token(ARG_CLOSE, 'p', -1)); | ||
arg = [1, 3]; | ||
} else { | ||
title.content += ': '; | ||
tokens.splice(arg[0] + 1, 0, title); | ||
++arg[1]; | ||
} | ||
|
||
let [...body] = findTokenPair(tokens, [BODY_OPEN, BODY_CLOSE], 1); | ||
|
||
tokens.at(0).attrSet('class', 'admonition'); | ||
tokens.at(0).attrJoin('class', kind.replace(':', '-')); | ||
tokens.at(arg[0]).attrSet('class', 'admonition-title'); | ||
|
||
[0, -1].map(tokens.at, tokens).forEach(token => { | ||
token.hidden = false; | ||
token.block = true; | ||
token.tag = 'aside'; | ||
}); | ||
|
||
arg.map(tokens.at, tokens).forEach(token => { | ||
token.hidden = false; | ||
token.block = true; | ||
token.tag = 'p'; | ||
}); | ||
|
||
body.map(tokens.at, tokens).forEach(token => { | ||
token.hidden = false; | ||
token.block = true; | ||
token.tag = 'div'; | ||
}); | ||
|
||
return true; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import type Token from 'markdown-it/lib/token'; | ||
|
||
export const ARG_OPEN = 'directive_arg_open'; | ||
export const ARG_CLOSE = 'directive_arg_close'; | ||
export const BODY_OPEN = 'directive_body_open'; | ||
export const BODY_CLOSE = 'directive_body_close'; | ||
|
||
/** | ||
* Find open/close pairs in a token array. | ||
*/ | ||
export function* findTokenPair( | ||
tokens: Token[], | ||
pair: [string, string], | ||
from: number = 0 | ||
): Generator<number> { | ||
|
||
let pos = from - 1; | ||
let depth = 0; | ||
|
||
while (++pos < tokens.length) { | ||
depth += tokens[pos].nesting; | ||
if (depth == 1 && tokens[pos].type == pair[0]) { | ||
yield pos; | ||
break; | ||
} | ||
} | ||
|
||
while (++pos < tokens.length) { | ||
depth += tokens[pos].nesting; | ||
if (depth == 0 && tokens[pos].type == pair[1]) { | ||
yield pos; | ||
break; | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es6", | ||
// module is overridden from the build:esm/build:cjs scripts | ||
"module": "es2015", | ||
"jsx": "react-jsx", | ||
"lib": ["es2020"], | ||
"esModuleInterop": true, | ||
"noImplicitAny": true, | ||
"strict": true, | ||
"strictNullChecks": false, | ||
"moduleResolution": "node", | ||
"sourceMap": false, | ||
// outDir is overridden from the build:esm/build:cjs scripts | ||
"outDir": "dist/types", | ||
"baseUrl": "src", | ||
"paths": { | ||
"*": ["node_modules/*"] | ||
}, | ||
// Type roots allows it to be included in a workspace | ||
"typeRoots": [ | ||
"./types", | ||
"./node_modules/@types", | ||
"../../node_modules/@types", | ||
"../../../node_modules/@types" | ||
], | ||
"resolveJsonModule": true, | ||
// Ignore node_modules, etc. | ||
"skipLibCheck": true, | ||
"forceConsistentCasingInFileNames": true | ||
}, | ||
"include": ["src/**/*"], | ||
"exclude": ["tests/**/*"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #417 we made quite a few changes for working as ESM packages only (and switching to vitest). I am happy to help out with upgrading here as well when you need it.