mirror of
https://github.com/danbulant/mdsvexrs
synced 2026-05-19 04:08:47 +00:00
initial commit
This commit is contained in:
commit
abd71d6263
10 changed files with 1352 additions and 0 deletions
9
.envrc
Normal file
9
.envrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# the shebang is ignored, but nice for editors
|
||||
|
||||
if type -P lorri &>/dev/null; then
|
||||
eval "$(lorri direnv)"
|
||||
else
|
||||
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
|
||||
use nix
|
||||
fi
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
node_modules
|
||||
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "markdown"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
markdown = { version = "1.0.0-alpha.21", features = ["serde"]}
|
||||
itertools = "0.13"
|
||||
serde_json = "1.0.133"
|
||||
serde_yaml = "0.9.34"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
regex = "1.11.1"
|
||||
clap = { version = "4.5.21", features = ["derive"] }
|
||||
syntext = "5.0"
|
||||
5
README.md
Normal file
5
README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# MDSvexRS
|
||||
|
||||
A faster markdown preprocessor for svelte. Compiles `.md` files into `.svelte` with non-reactive blocks wrapped in direct HTML for faster compilation and rendering.
|
||||
|
||||
Note that this, like the original MDSvex, trusts it's input and doesn't escape HTML or script files.
|
||||
508
highlighter/hanekawa.json
Normal file
508
highlighter/hanekawa.json
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
{
|
||||
"type": "dark",
|
||||
"name": "Hanekawa",
|
||||
"colors": {
|
||||
"foreground": "#c3c3c3",
|
||||
"editor.foreground": "#F8F8F2",
|
||||
"dropdown.foreground": "#c3c3c3",
|
||||
"panelTitle.activeForeground": "#c3c3c3",
|
||||
"panelTitle.inactiveForeground": "#c3c3c3",
|
||||
"gitDecoration.ignoredResourceForeground": "#6f6f6f",
|
||||
"list.invalidItemForeground": "#6f6f6f",
|
||||
"list.errorForeground": "#FF5555",
|
||||
"editorError.foreground": "#FF5555",
|
||||
"input.background": "#1b1820AA",
|
||||
"peekViewResult.background": "#1b1820",
|
||||
"peekViewResult.fileForeground": "#988ccb",
|
||||
"peekViewTitleDescription.foreground": "#988ccb",
|
||||
"list.focusBackground": "#5a3b5d",
|
||||
"quickInput.list.focusBackground": "#5a3b5dAA",
|
||||
"quickInput.foreground": "#c3c3c3",
|
||||
"peekViewTitleLabel.foreground": "#c3c3c3",
|
||||
"selection.background": "#5a3b5d",
|
||||
"editorGutter.modifiedBackground": "#152A3D",
|
||||
"editorGutter.addedBackground": "#142F14",
|
||||
"editorGutter.deletedBackground": "#1D1D1D",
|
||||
"editorUnnecessaryCode.border": "#72737A",
|
||||
"editorGroup.dropBackground": "#262626AA",
|
||||
"panelSection.dropBackground": "#262626AA",
|
||||
"statusBarItem.prominentBackground": "#5a3b5dAA",
|
||||
"statusBarItem.prominentForeground": "#ebd6fc",
|
||||
"keybindingLabel.background": "#2e2939",
|
||||
"keybindingLabel.foreground": "#c7c7c7",
|
||||
"keybindingLabel.bottomBorder": "#211c23",
|
||||
"settings.sashBorder": "#211c23",
|
||||
"tab.hoverBackground": "#262129AA",
|
||||
"list.activeSelectionBackground": "#5a3b5d",
|
||||
"peekViewResult.selectionBackground": "#5a3b5d",
|
||||
"list.hoverBackground": "#5a3b5d7a",
|
||||
"editor.selectionHighlightBackground": "#262626",
|
||||
"editor.linkedEditingBackground": "#262626",
|
||||
"editor.selectionBackground": "#5a3b5dAA",
|
||||
"minimap.selectionHighlight": "#5a3b5d",
|
||||
"list.activeSelectionForeground": "#ebd6fc",
|
||||
"list.focusForeground": "#ebd6fc",
|
||||
"pickerGroup.foreground": "#988ccb",
|
||||
"descriptionForeground": "#988ccb",
|
||||
"gitlens.trailingLineForegroundColor": "#988ccb99",
|
||||
"input.placeholderForeground": "#988ccb",
|
||||
"list.deemphasizedForeground": "#988ccb",
|
||||
"inputValidation.errorBackground": "#36151F",
|
||||
"minimap.errorHighlight": "#FF5555AA",
|
||||
"inputValidation.errorForeground": "#FF5555",
|
||||
"inputValidation.warningBackground": "#191a18",
|
||||
"list.inactiveSelectionBackground": "#36233899",
|
||||
"list.inactiveSelectionForeground": "#c3c3c3",
|
||||
"editor.background": "#100d11",
|
||||
"errorForeground": "#FF5555",
|
||||
"tab.activeBackground": "#262129",
|
||||
"panelSectionHeader.background": "#0f0d10",
|
||||
"focusBorder": "#211c23",
|
||||
"pickerGroup.border": "#211c23",
|
||||
"tab.border": "#211c23",
|
||||
"tree.indentGuidesStroke": "#211c23",
|
||||
"sideBar.border": "#211c23",
|
||||
"editorIndentGuide.background": "#211c23",
|
||||
"editorIndentGuide.activeBackground": "#211c23",
|
||||
"inputOption.activeBorder": "#211c23",
|
||||
"textBlockQuote.border": "#211c23",
|
||||
"dropdown.border": "#211c23",
|
||||
"input.border": "#211c23",
|
||||
"activityBar.border": "#211c23",
|
||||
"editorGroup.border": "#211c23",
|
||||
"editorOverviewRuler.border": "#211c23",
|
||||
"editorError.border": "#36151F",
|
||||
"editorWarning.border": "#211c23",
|
||||
"editorInfo.border": "#211c23",
|
||||
"editorHint.border": "#211c23",
|
||||
"editorHoverWidget.border": "#211c23",
|
||||
"debugExceptionWidget.border": "#211c23",
|
||||
"merge.border": "#211c23",
|
||||
"panel.border": "#211c23",
|
||||
"statusBar.border": "#211c23",
|
||||
"titleBar.border": "#211c23",
|
||||
"notificationCenter.border": "#211c23",
|
||||
"notifications.border": "#211c23",
|
||||
"editorCursor.background": "#100d11",
|
||||
"terminalCursor.background": "#100d11",
|
||||
"terminalCursor.foreground": "#7660A4",
|
||||
"editorSuggestWidget.border": "#100d11",
|
||||
"panel.background": "#100d11",
|
||||
"sideBar.background": "#100d11",
|
||||
"tab.inactiveBackground": "#100d11",
|
||||
"editorGroupHeader.tabsBackground": "#100d11",
|
||||
"dropdown.background": "#100d11",
|
||||
"editorLineNumber.foreground": "#777495",
|
||||
"gitlens.gutterForegroundColor": "#777495",
|
||||
"checkbox.border": "#7e7997",
|
||||
"checkbox.foreground": "#7660A4",
|
||||
"toolbar.hoverBackground": "#262129",
|
||||
"toolbar.hoverOutline": "#262129",
|
||||
"toolbar.activeBackground": "#262129",
|
||||
"textLink.foreground": "#7660A4AA",
|
||||
"textLink.activeForeground": "#7660A4",
|
||||
"settings.modifiedItemIndicator": "#7660A4",
|
||||
"textBlockQuote.background": "#281f28",
|
||||
"textCodeBlock.background": "#281f28",
|
||||
"textSeparator.foreground": "#211c23",
|
||||
"editorCursor.foreground": "#7660A4",
|
||||
"editorWidget.border": "#7660A4",
|
||||
"list.highlightForeground": "#8968dc",
|
||||
"editorLineNumber.activeForeground": "#7660A4",
|
||||
"debugIcon.breakpointForeground": "#7660A4",
|
||||
"tab.activeBorder": "#7660A4",
|
||||
"peekView.border": "#7660A4",
|
||||
"statusBar.background": "#7660A4",
|
||||
"statusBarItem.remoteBackground": "#5a3b5d",
|
||||
"statusBarItem.remoteForeground": "#ebd6fc",
|
||||
"statusBar.noFolderBackground": "#7660A4",
|
||||
"panelTitle.activeBorder": "#7660A4",
|
||||
"statusBar.debuggingBackground": "#7660A4",
|
||||
"editorBracketMatch.border": "#7660A4",
|
||||
"gitDecoration.modifiedResourceForeground": "#7660A4",
|
||||
"activityBar.foreground": "#7660A4",
|
||||
"activityBarBadge.background": "#7660A4",
|
||||
"activityBarBadge.foreground": "#141115",
|
||||
"badge.background": "#7660A4",
|
||||
"badge.foreground": "#141115",
|
||||
"progressBar.background": "#7660A4",
|
||||
"scrollbarSlider.hoverBackground": "#7660A49A",
|
||||
"notificationToast.border": "#7660A49A",
|
||||
"scrollbarSlider.background": "#7660A45A",
|
||||
"scrollbarSlider.activeBackground": "#7660A49A",
|
||||
"activityBar.inactiveForeground": "#7660A4BB",
|
||||
"button.background": "#2e2939",
|
||||
"button.foreground": "#c7c7c7",
|
||||
"button.secondaryForeground": "#c7c7c7BB",
|
||||
"button.secondaryBackground": "#2e2939CC",
|
||||
"editor.lineHighlightBackground": "#14111577",
|
||||
"gitlens.trailingLineBackgroundColor": "#1e1d1f77",
|
||||
"editor.lineHighlightBorder": "#14111500",
|
||||
"sideBarSectionHeader.background": "#141115",
|
||||
"activityBar.background": "#141115",
|
||||
"editorGutter.background": "#00000000",
|
||||
"symbolIcon.arrayForeground": "#86dbfd",
|
||||
"symbolIcon.constantForeground": "#86dbfd",
|
||||
"symbolIcon.enumeratorMemberForeground": "#86dbfd",
|
||||
"symbolIcon.booleanForeground": "#d55fde",
|
||||
"symbolIcon.classForeground": "#e39656",
|
||||
"symbolIcon.enumeratorForeground": "#7ceec8",
|
||||
"symbolIcon.fieldForeground": "#7ceec8",
|
||||
"symbolIcon.constructorForeground": "#e39656",
|
||||
"symbolIcon.functionForeground": "#9a97f4",
|
||||
"symbolIcon.colorForeground": "#7660A4",
|
||||
"symbolIcon.eventForeground": "#7660A4",
|
||||
"symbolIcon.fileForeground": "#7e7997",
|
||||
"symbolIcon.folderForeground": "#7e7997",
|
||||
"symbolIcon.interfaceForeground": "#F6E3CC",
|
||||
"editor.findMatchBackground": "#5a3b5d",
|
||||
"editor.findMatchBorder": "#7660A4",
|
||||
"editor.wordHighlightStrongBackground": "#262626AA",
|
||||
"editor.snippetTabstopHighlightBackground": "#262626AA",
|
||||
"editor.findMatchHighlightBackground": "#305D49AA",
|
||||
"peekViewEditor.matchHighlightBackground": "#362338",
|
||||
"editor.wordHighlightBackground": "#262626AA",
|
||||
"editorOverviewRuler.findMatchForeground": "#362338",
|
||||
"searchEditor.findMatchBackground": "#305D49",
|
||||
"peekViewResult.matchHighlightBackground": "#362338",
|
||||
"editorWidget.background": "#0f0d10",
|
||||
"debugToolBar.background": "#0f0d10",
|
||||
"debugView.stateLabelBackground": "#262129",
|
||||
"debugTokenExpression.string": "#F6E3CC",
|
||||
"debugTokenExpression.boolean": "#d55fde",
|
||||
"debugTokenExpression.value": "#988ccb",
|
||||
"debugTokenExpression.number": "#86dbfd",
|
||||
"debugTokenExpression.error": "#FF5555",
|
||||
"debugTokenExpression.name": "#e39656",
|
||||
"breadcrumb.foreground": "#777495",
|
||||
"breadcrumb.focusForeground": "#988ccb",
|
||||
"breadcrumb.activeSelectionForeground": "#9a97f4",
|
||||
"testing.iconFailed": "#FF5555",
|
||||
"testing.iconErrored": "#FF5555",
|
||||
"testing.runAction": "#7660A4",
|
||||
"welcomePage.tileBackground": "#281f28AA",
|
||||
"welcomePage.buttonBackground": "#281f28AA",
|
||||
"welcomePage.tileHoverBackground": "#5a3b5dAA",
|
||||
"welcomePage.buttonHoverBackground": "#5a3b5dAA",
|
||||
"testing.message.hint.decorationForeground": "#988ccb",
|
||||
"settings.focusedRowBackground": "#262129",
|
||||
"notebook.focusedRowBorder": "#262129",
|
||||
"notebook.rowHoverBackground": "#1e1d1f",
|
||||
"debugView.valueChangedHighlight": "#5a3b5d99",
|
||||
"editorSuggestWidget.background": "#0f0d10",
|
||||
"notifications.background": "#0f0d1080",
|
||||
"peekViewTitle.background": "#0f0d10",
|
||||
"widget.shadow": "#1111115a",
|
||||
"scrollbar.shadow": "#1111113a",
|
||||
"titleBar.activeBackground": "#0f0d10",
|
||||
"titleBar.inactiveBackground": "#0f0d10",
|
||||
"gitlens.gutterBackgroundColor": "#281f28",
|
||||
"peekViewEditor.background": "#281f28",
|
||||
"peekViewEditorGutter.background": "#281f28",
|
||||
"terminal.ansiRed": "#E356A7",
|
||||
"terminal.ansiGreen": "#42E66C",
|
||||
"terminal.ansiYellow": "#EFA554",
|
||||
"terminal.ansiBlue": "#9B6BDF",
|
||||
"terminal.ansiMagenta": "#E64747",
|
||||
"terminal.ansiCyan": "#75D7EC",
|
||||
"editor.stackFrameHighlightBackground": "#5a3b5d",
|
||||
"debugIcon.breakpointCurrentStackframeForeground": "#5a3b5d",
|
||||
"debugIcon.breakpointDisabledForeground": "#6f6f6f",
|
||||
"debugIcon.breakpointUnverifiedForeground": "#72737A",
|
||||
"debugIcon.startForeground": "#7660A4",
|
||||
"debugIcon.continueForeground": "#7660A4",
|
||||
"debugIcon.stepOverForeground": "#9B6BDF",
|
||||
"debugIcon.stepIntoForeground": "#EFA554",
|
||||
"debugIcon.stopForeground": "#FF5555",
|
||||
"debugIcon.stepOutForeground": "#75D7EC",
|
||||
"debugIcon.restartForeground": "#42E66C",
|
||||
"debugConsole.infoForeground": "#988ccb",
|
||||
"debugConsole.errorForeground": "#FF5555",
|
||||
"debugConsole.sourceForeground": "#9a97f4",
|
||||
"debugIcon.breakpointStackframeForeground": "#362338",
|
||||
"editor.focusedStackFrameHighlightBackground": "#362338",
|
||||
"diffEditor.removedTextBackground": "#1D1D1D50",
|
||||
"diffEditor.insertedTextBackground": "#142F1450",
|
||||
"gitlens.gutterUncommittedForegroundColor": "#152A3D",
|
||||
"gitlens.lineHighlightBackgroundColor": "#142F1450",
|
||||
"charts.red": "#E356A7",
|
||||
"charts.blue": "#9B6BDF",
|
||||
"charts.yellow": "#EFA554",
|
||||
"charts.orange": "#9a97f4",
|
||||
"charts.green": "#42E66C",
|
||||
"charts.purple": "#7ceec8",
|
||||
"editorBracketHighlight.foreground1": "#F8F8F2",
|
||||
"editorBracketHighlight.foreground2": "#7ceec8",
|
||||
"editorBracketHighlight.foreground3": "#e39656",
|
||||
"editorBracketHighlight.foreground4": "#9a97f4",
|
||||
"editorBracketHighlight.foreground5": "#7ceec8",
|
||||
"editorBracketHighlight.foreground6": "#86dbfd",
|
||||
"editorBracketHighlight.unexpectedBracket.foreground": "#FF5555"
|
||||
},
|
||||
"semanticHighlighting": true,
|
||||
"semanticTokenColors": {
|
||||
"variable.readonly.local": "#F8F8F2",
|
||||
"function.declaration": "#e39656"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"emphasis"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"strong"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"invalid"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#FF5555",
|
||||
"fontStyle": "underline strikethrough"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"invalid.deprecated"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#988ccb",
|
||||
"fontStyle": "underline italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Underlined markup",
|
||||
"scope": [
|
||||
"markup.underline"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "underline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bold markup",
|
||||
"scope": [
|
||||
"markup.bold"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Markup italic",
|
||||
"scope": [
|
||||
"markup.italic"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.property-name.json",
|
||||
"meta.object-literal.key",
|
||||
"variable.object.property",
|
||||
"variable.other.readwrite.alias",
|
||||
"variable.other.property",
|
||||
"meta.brace"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#F8F8F2",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"source",
|
||||
"punctuation.definition.parameters",
|
||||
"punctuation.definition.block",
|
||||
"text.html.markdown",
|
||||
"variable.other.constant",
|
||||
"meta.definition.variable",
|
||||
"variable.other.constant.js",
|
||||
"variable.other.constant.ts"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#F8F8F2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable.parameter",
|
||||
"meta.parameters",
|
||||
"meta.function-call.arguments",
|
||||
"markup.inline.raw.string.markdown",
|
||||
"string"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#F6E3CC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.language.json",
|
||||
"keyword",
|
||||
"storage.type",
|
||||
"storage.modifier",
|
||||
"keyword.operator.optional",
|
||||
"keyword.operator.new",
|
||||
"keyword.operator.instanceof",
|
||||
"keyword.operator.expression.typeof",
|
||||
"constant.language.boolean",
|
||||
"variable.language.this",
|
||||
"constant.language.undefined",
|
||||
"constant.language.java",
|
||||
"constant.language.python",
|
||||
"support.type.primitive.ts",
|
||||
"markup.heading.setext",
|
||||
"punctuation.definition.raw.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#d55fde"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable.other.constant",
|
||||
"meta.definition.method",
|
||||
"meta.definition.function",
|
||||
"meta.embedded.line",
|
||||
"markup.inline.raw.string",
|
||||
"variable.css",
|
||||
"meta.function",
|
||||
"support.class",
|
||||
"meta.method.identifier",
|
||||
"variable.other.readwrite.alias"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#e39656"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator",
|
||||
"entity.name.tag",
|
||||
"meta.template.expression",
|
||||
"punctuation.definition.list.begin",
|
||||
"support.class.component.svelte",
|
||||
"punctuation.definition.link.restructuredtext",
|
||||
"punctuation.definition.typeparameters"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#ff8fec"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.type.class",
|
||||
"meta.type",
|
||||
"variable.other.object",
|
||||
"entity.name.type",
|
||||
"markup.heading",
|
||||
"string.other.link.title",
|
||||
"storage.modifier.import",
|
||||
"meta.import",
|
||||
"storage.type.java",
|
||||
"support.type.property-name",
|
||||
"meta.property-name"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#7ceec8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.type.primitive",
|
||||
"entity.name.type.interface",
|
||||
"variable.parameter.function-call"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#7ceec8",
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"meta.function-call",
|
||||
"meta.decorator",
|
||||
"meta.link",
|
||||
"meta.method-call",
|
||||
"meta.function-call.ts",
|
||||
"storage.type.annotation",
|
||||
"meta.declaration.annotation",
|
||||
"support.function",
|
||||
"meta.assertion.look-ahead.regexp",
|
||||
"entity.name.function"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9a97f4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.other.attribute-name"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#9a97f4",
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"support.type.object.module",
|
||||
"variable.other.enummember",
|
||||
"meta.enum.declaration",
|
||||
"constant.other.color",
|
||||
"variable.language.special",
|
||||
"support.constant",
|
||||
"meta.type.parameters",
|
||||
"fenced_code.block.language.markdown",
|
||||
"entity.name.section.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#86dbfd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [],
|
||||
"settings": {
|
||||
"foreground": "#86dbfd",
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"comment",
|
||||
"markup.quote"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#79769f",
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"markup.fenced_code.block"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [],
|
||||
"settings": {
|
||||
"foreground": "#988ccb"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
95
highlighter/index.js
Normal file
95
highlighter/index.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { createHighlighter } from 'shiki'
|
||||
import { readFileSync } from 'fs'
|
||||
import readline from "readline/promises"
|
||||
import {
|
||||
transformerNotationDiff,
|
||||
transformerNotationHighlight,
|
||||
transformerNotationWordHighlight,
|
||||
transformerNotationErrorLevel,
|
||||
transformerMetaHighlight,
|
||||
transformerMetaWordHighlight,
|
||||
transformerNotationFocus
|
||||
} from "@shikijs/transformers"
|
||||
|
||||
const theme = JSON.parse(readFileSync('hanekawa.json', 'utf-8'))
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
const highlighter = createHighlighter({
|
||||
langs: ["javascript", "rust", "c#", "c", "asm", "sh", "ts"],
|
||||
themes: [theme]
|
||||
})
|
||||
|
||||
const tokenMap = {
|
||||
fn: "meta.declaration.annotation"
|
||||
}
|
||||
|
||||
let loadedLangs;
|
||||
let sum = 0
|
||||
|
||||
function time(start) {
|
||||
let time = performance.now() - start
|
||||
sum += time
|
||||
// console.error(sum)
|
||||
}
|
||||
|
||||
rl.on('line', async (line) => {
|
||||
let start = performance.now();
|
||||
const data = JSON.parse(line)
|
||||
let { lang, inline, code, meta } = data
|
||||
if(lang[0] == ".") {
|
||||
let scope = lang.slice(1)
|
||||
let color
|
||||
if(tokenMap[scope]) scope = tokenMap[scope]
|
||||
for(let tokens of theme.tokenColors) {
|
||||
if(tokens.scope?.includes(scope)) {
|
||||
color = tokens.settings.foreground
|
||||
break
|
||||
}
|
||||
}
|
||||
let html
|
||||
// shiki does this for us, but we need to do it manually here
|
||||
code = simplehtmlentities(code)
|
||||
if(color) {
|
||||
html = `<code data-pretty-code-figure style="color: ${color}">${code}</code>`
|
||||
} else {
|
||||
html = code
|
||||
}
|
||||
time(start)
|
||||
console.log(JSON.stringify({ html, elapsed: performance.now() - start, sum }))
|
||||
return
|
||||
}
|
||||
let shiki = await highlighter
|
||||
if(!loadedLangs) loadedLangs = shiki.getLoadedLanguages()
|
||||
if(!loadedLangs.includes(lang)) {
|
||||
await shiki.loadLanguage(lang)
|
||||
loadedLangs = shiki.getLoadedLanguages()
|
||||
}
|
||||
let html = shiki.codeToHtml(code, {
|
||||
lang,
|
||||
structure: inline ? 'inline' : 'classic',
|
||||
theme: "Hanekawa",
|
||||
meta: {
|
||||
"data-pretty-code": "",
|
||||
__raw: meta
|
||||
},
|
||||
transformers: [
|
||||
transformerNotationDiff(),
|
||||
transformerNotationHighlight(),
|
||||
transformerNotationWordHighlight(),
|
||||
transformerNotationErrorLevel(),
|
||||
transformerNotationFocus()
|
||||
]
|
||||
})
|
||||
if(inline) html = `<code data-pretty-code-figure>${html}</code>`
|
||||
time(start)
|
||||
let out = JSON.stringify({ html, elapsed: performance.now() - start, sum });
|
||||
console.log(out)
|
||||
})
|
||||
|
||||
function simplehtmlentities(str) {
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
||||
}
|
||||
15
highlighter/package.json
Normal file
15
highlighter/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "highlighter",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"public": false,
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@shikijs/transformers": "^1.22.2",
|
||||
"shiki": "^1.24.0"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Daniel Bulant"
|
||||
}
|
||||
9
package.json
Normal file
9
package.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "markdown",
|
||||
"scripts": {
|
||||
"build": "cargo build --release"
|
||||
},
|
||||
"dependencies": {
|
||||
"highlighter": "workspace:*"
|
||||
}
|
||||
}
|
||||
38
shell.nix
Normal file
38
shell.nix
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{ pkgs ? import <nixpkgs> {}, pkgsun ? import <nixos-unstable> {} }:
|
||||
let
|
||||
fenix = import (fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz") { };
|
||||
rust-toolchain =
|
||||
fenix.default.toolchain;
|
||||
in
|
||||
pkgs.mkShell rec {
|
||||
buildInputs = with pkgs;[
|
||||
openssl
|
||||
pkg-config
|
||||
cmake
|
||||
zlib
|
||||
rust-toolchain
|
||||
|
||||
libclang
|
||||
llvmPackages.clang
|
||||
openssl
|
||||
clang
|
||||
llvmPackages.libclang.lib stdenv.cc.libc
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
fontconfig
|
||||
rustPlatform.bindgenHook
|
||||
];
|
||||
|
||||
preConfigure = with pkgs;''
|
||||
export BINDGEN_EXTRA_CLANG_ARGS="-isystem ${clang}/resource-root/include $NIX_CFLAGS_COMPILE"
|
||||
'';
|
||||
|
||||
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}";
|
||||
OPENSSL_DIR="${pkgs.openssl.dev}";
|
||||
OPENSSL_LIB_DIR="${pkgs.openssl.out}/lib";
|
||||
RUST_SRC_PATH = "${pkgsun.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
RUST_PATH="${rust-toolchain}";
|
||||
RUST_LOG="debug";
|
||||
LIBCLANG_PATH = "${pkgs.llvmPackages_14.libclang.lib}/lib";
|
||||
}
|
||||
657
src/main.rs
Normal file
657
src/main.rs
Normal file
|
|
@ -0,0 +1,657 @@
|
|||
use std::{io::{stdin, BufRead, BufReader, Read, Write}, process::{Child, ChildStdout, Command, Stdio}, sync::LazyLock, time::{Duration, Instant}};
|
||||
|
||||
use itertools::Itertools;
|
||||
use markdown::{mdast::{Blockquote, Break, Code, Definition, Delete, Emphasis, FootnoteDefinition, FootnoteReference, Heading, Html, Image, ImageReference, InlineCode, InlineMath, Link, LinkReference, List, ListItem, Math, MdxFlowExpression, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Node, Paragraph, Root, Strong, Table, TableCell, TableRow, Text, ThematicBreak, Toml, Yaml}, unist::Position, Constructs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ToHtmlResult {
|
||||
html: String,
|
||||
svelte: bool
|
||||
}
|
||||
|
||||
impl ToHtmlResult {
|
||||
fn new(html: String, svelte: bool) -> Self {
|
||||
Self { html, svelte }
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Self::new("".to_string(), false)
|
||||
}
|
||||
}
|
||||
|
||||
trait ToHtml {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult;
|
||||
|
||||
fn visit(&self, _ctx: &mut Context) {}
|
||||
}
|
||||
|
||||
impl<T> ToHtml for Vec<T> where T: ToHtml {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
merge(&self.iter().map(|t| t.to_html(ctx)).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn visit(&self, ctx: &mut Context) {
|
||||
for t in self {
|
||||
t.visit(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Root {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
self.children.to_html(ctx)
|
||||
}
|
||||
|
||||
fn visit(&self, ctx: &mut Context) {
|
||||
self.children.visit(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Blockquote {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<blockquote>{}</blockquote>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for FootnoteDefinition {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn visit(&self, _ctx: &mut Context) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for MdxJsxFlowElement {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for List {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
// todo!()
|
||||
let litype = match self.ordered {
|
||||
true => "ol",
|
||||
false => "ul"
|
||||
};
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<{}>{}</{}>", litype, children.html, litype), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for MdxjsEsm {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Toml {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Yaml {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
ToHtmlResult::empty()
|
||||
}
|
||||
|
||||
fn visit(&self, ctx: &mut Context) {
|
||||
let value = serde_yaml::from_str(&self.value).unwrap();
|
||||
ctx.yaml = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Break {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
ToHtmlResult::new("<br>".to_string(), false)
|
||||
}
|
||||
}
|
||||
|
||||
static LANG_HINT_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| regex::Regex::new(r"\{:(?<lang>[\w.]+)\}$").unwrap());
|
||||
|
||||
impl ToHtml for InlineCode {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let value = &self.value;
|
||||
// if value ends with {lang} then it's a language hint
|
||||
let output = if let Some(caps) = LANG_HINT_REGEX.captures(value) {
|
||||
let lang = &caps["lang"];
|
||||
let code = &value[..value.len() - lang.len() - 3];
|
||||
ctx.highlight(HighlightRequest {
|
||||
lang: lang.to_string(),
|
||||
inline: true,
|
||||
code: code.to_string(),
|
||||
meta: None
|
||||
})
|
||||
} else if let Some(lang) = &ctx.default_lang {
|
||||
ctx.highlight(HighlightRequest {
|
||||
lang: lang.clone(),
|
||||
inline: true,
|
||||
code: value.clone(),
|
||||
meta: None
|
||||
})
|
||||
} else {
|
||||
format!("<code>{}</code>", html_encode(&self.value))
|
||||
};
|
||||
ToHtmlResult::new(output, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for InlineMath {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Delete {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Emphasis {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<em>{}</em>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for MdxTextExpression {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for FootnoteReference {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Html {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let value = self.value.clone();
|
||||
if value.starts_with("<script") {
|
||||
ctx.script = Some(value);
|
||||
|
||||
return ToHtmlResult::empty();
|
||||
}
|
||||
ToHtmlResult::new(value, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Image {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
let alt = &self.alt;
|
||||
let title = self.title.as_ref().map(|t| format!(" title=\"{}\"", t)).unwrap_or_default();
|
||||
let url = &self.url;
|
||||
ToHtmlResult::new(format!("<img src=\"{}\" alt=\"{}\"{}>", url, alt, title), false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for ImageReference {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for MdxJsxTextElement {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Link {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
let title = self.title.as_ref().map(|t| format!(" title=\"{}\"", t)).unwrap_or_default();
|
||||
ToHtmlResult::new(format!("<a href=\"{}\"{}>{}</a>", self.url, title, children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for LinkReference {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Strong {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<strong>{}</strong>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Text {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
ToHtmlResult::new(html_encode(&self.value), false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Code {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let value = &self.value;
|
||||
let lang = self.lang.as_ref().or(ctx.default_lang.as_ref());
|
||||
let highlighted = if let Some(lang) = lang {
|
||||
ctx.highlight(HighlightRequest {
|
||||
code: value.clone(),
|
||||
inline: false,
|
||||
lang: lang.clone(),
|
||||
meta: self.meta.clone()
|
||||
})
|
||||
} else {
|
||||
format!("<pre><code>{}</code></pre>", html_encode(&self.value))
|
||||
};
|
||||
ToHtmlResult::new(highlighted, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Math {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for MdxFlowExpression {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn slug(str: &str) -> String {
|
||||
str.to_lowercase().replace(" ", "-")
|
||||
}
|
||||
|
||||
impl ToHtml for Heading {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
let text = self.children.iter().filter_map(|c| {
|
||||
match c {
|
||||
Node::Text(t) => Some(t.value.clone()),
|
||||
_ => None
|
||||
}
|
||||
}).join("");
|
||||
let mut slug = slug(&text);
|
||||
if ctx.titles.iter().any(|t| t.id == slug) {
|
||||
let mut i = 1;
|
||||
while ctx.titles.iter().any(|t| t.id == format!("{}-{}", slug, i)) {
|
||||
i += 1;
|
||||
}
|
||||
slug = format!("{}-{}", slug, i);
|
||||
}
|
||||
ctx.titles.push(Title {
|
||||
level: self.depth,
|
||||
text: text.clone(),
|
||||
id: slug.clone(),
|
||||
pos: self.position.clone()
|
||||
});
|
||||
ToHtmlResult::new(format!("\n<h{} id=\"{}\">{}</h{}>\n", self.depth, slug, children.html, self.depth), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Table {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<table>{}</table>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for ThematicBreak {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
ToHtmlResult::new("\n<hr>\n".to_string(), false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for TableRow {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<tr>{}</tr>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for TableCell {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<td>{}</td>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for ListItem {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<li>{}</li>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Definition {
|
||||
fn to_html(&self, _ctx: &mut Context) -> ToHtmlResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHtml for Paragraph {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
let children = self.children.to_html(ctx);
|
||||
ToHtmlResult::new(format!("<p>{}</p>", children.html), children.svelte)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! node_impl {
|
||||
($($node:ident($name:ident)),+) => {
|
||||
impl ToHtml for Node {
|
||||
fn to_html(&self, ctx: &mut Context) -> ToHtmlResult {
|
||||
match self {
|
||||
$(markdown::mdast::Node::$node($name) => $name.to_html(ctx)),+
|
||||
}
|
||||
}
|
||||
fn visit(&self, ctx: &mut Context) {
|
||||
match self {
|
||||
$(markdown::mdast::Node::$node($name) => $name.visit(ctx)),+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node_impl!(
|
||||
Root(root),
|
||||
Blockquote(blockquote),
|
||||
FootnoteDefinition(footnote_definition),
|
||||
MdxJsxFlowElement(mdx_jsx_flow_element),
|
||||
List(list),
|
||||
MdxjsEsm(mdxjs_esm),
|
||||
Toml(toml),
|
||||
Yaml(yaml),
|
||||
Break(rbreak),
|
||||
InlineCode(inline_code),
|
||||
InlineMath(inline_math),
|
||||
Delete(delete),
|
||||
Emphasis(emphasis),
|
||||
MdxTextExpression(mdx_text_expression),
|
||||
FootnoteReference(footnote_reference),
|
||||
Html(html),
|
||||
Image(image),
|
||||
ImageReference(image_reference),
|
||||
MdxJsxTextElement(mdx_jsx_text_element),
|
||||
Link(link),
|
||||
LinkReference(link_reference),
|
||||
Strong(strong),
|
||||
Text(text),
|
||||
Code(code),
|
||||
Math(math),
|
||||
MdxFlowExpression(mdx_flow_expression),
|
||||
Heading(heading),
|
||||
Table(table),
|
||||
ThematicBreak(thematic_break),
|
||||
TableRow(table_row),
|
||||
TableCell(table_cell),
|
||||
ListItem(list_item),
|
||||
Definition(definition),
|
||||
Paragraph(paragraph)
|
||||
);
|
||||
|
||||
fn svelte_html_encode(string: String) -> String {
|
||||
String::from("{@html `") + &string.replace("\\", "\\\\").replace("`", "\\`") + "`}"
|
||||
}
|
||||
|
||||
fn merge(results: &[ToHtmlResult]) -> ToHtmlResult {
|
||||
let chunked = results.iter().chunk_by(|r| r.svelte);
|
||||
let mut html = chunked.into_iter().map(|(svelte, results)| {
|
||||
let html = results.map(|r| r.html.clone()).join("");
|
||||
ToHtmlResult::new(html, svelte)
|
||||
});
|
||||
let Some(first) = html.next() else {
|
||||
return ToHtmlResult::new("".to_string(), false);
|
||||
};
|
||||
if let Some(second) = html.next() {
|
||||
ToHtmlResult::new(
|
||||
[first, second].into_iter().chain(html).map(|r| {
|
||||
if r.html.is_empty() {
|
||||
return "".to_string();
|
||||
}
|
||||
if r.svelte {
|
||||
r.html
|
||||
} else {
|
||||
svelte_html_encode(r.html)
|
||||
}
|
||||
}).join(""),
|
||||
true
|
||||
)
|
||||
} else {
|
||||
first
|
||||
}
|
||||
}
|
||||
|
||||
// from markdown-rs
|
||||
pub fn html_encode(value: &str) -> String {
|
||||
let encode_html = true; // originally a param
|
||||
// It’ll grow a bit bigger for each dangerous character.
|
||||
let mut result = String::with_capacity(value.len());
|
||||
let bytes = value.as_bytes();
|
||||
let mut index = 0;
|
||||
let mut start = 0;
|
||||
|
||||
while index < bytes.len() {
|
||||
let byte = bytes[index];
|
||||
if matches!(byte, b'\0') || (encode_html && matches!(byte, b'&' | b'"' | b'<' | b'>')) {
|
||||
result.push_str(&value[start..index]);
|
||||
result.push_str(match byte {
|
||||
b'\0' => "<EFBFBD>",
|
||||
b'&' => "&",
|
||||
b'"' => """,
|
||||
b'<' => "<",
|
||||
// `b'>'`
|
||||
_ => ">",
|
||||
});
|
||||
|
||||
start = index + 1;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
result.push_str(&value[start..]);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn finish(res: ToHtmlResult) -> String {
|
||||
if res.svelte {
|
||||
res.html
|
||||
} else {
|
||||
svelte_html_encode(res.html)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Title {
|
||||
level: u8,
|
||||
text: String,
|
||||
id: String,
|
||||
pos: Option<Position>
|
||||
}
|
||||
|
||||
struct Context {
|
||||
child: Child,
|
||||
bufread: BufReader<ChildStdout>,
|
||||
yaml: Option<serde_json::Map<String, Value>>,
|
||||
default_lang: Option<String>,
|
||||
script: Option<String>,
|
||||
args: Args,
|
||||
titles: Vec<Title>,
|
||||
|
||||
highlight_times: Duration,
|
||||
js_times: Duration,
|
||||
js_sum: f64
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HighlightRequest {
|
||||
code: String,
|
||||
inline: bool,
|
||||
lang: String,
|
||||
meta: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct HighlightResponse {
|
||||
html: String,
|
||||
|
||||
elapsed: f64,
|
||||
sum: f64
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new(args: Args) -> Self {
|
||||
let mut child = Command::new("node")
|
||||
.arg(".")
|
||||
.current_dir("highlighter")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn().unwrap();
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let bufread = BufReader::new(stdout);
|
||||
|
||||
Context { child, bufread, yaml: None, titles: Vec::new(), default_lang: None, script: None, args, highlight_times: Duration::ZERO, js_times: Duration::ZERO, js_sum: 0. }
|
||||
}
|
||||
|
||||
fn highlight(&mut self, code: HighlightRequest) -> String {
|
||||
let start = Instant::now();
|
||||
|
||||
let stdin = self.child.stdin.as_mut().unwrap();
|
||||
let data = serde_json::to_string(&code).unwrap() + "\n";
|
||||
stdin.write_all(data.as_bytes()).unwrap();
|
||||
|
||||
let mut buf = String::new();
|
||||
let _line = self.bufread.read_line(&mut buf).unwrap();
|
||||
let res: HighlightResponse = serde_json::from_str(&buf).unwrap();
|
||||
|
||||
self.highlight_times += start.elapsed();
|
||||
self.js_times += Duration::from_nanos((res.elapsed * 1_000_000.) as u64);
|
||||
self.js_sum = res.sum;
|
||||
res.html
|
||||
}
|
||||
|
||||
fn resolve_layout(&self) -> &str {
|
||||
&self.args.layout
|
||||
// Path::new(&self.args.layout).
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
layout: String,
|
||||
#[arg(short, long)]
|
||||
path: String,
|
||||
#[arg(long)]
|
||||
timings: bool
|
||||
}
|
||||
|
||||
/// Converts markdown to svelte code, MDSvex alternative. Expects trusted code!
|
||||
fn main() {
|
||||
let options = markdown::ParseOptions {
|
||||
constructs: Constructs {
|
||||
attention: true,
|
||||
autolink: true,
|
||||
block_quote: true,
|
||||
character_escape: true,
|
||||
character_reference: true,
|
||||
code_indented: true,
|
||||
code_fenced: true,
|
||||
code_text: true,
|
||||
definition: true,
|
||||
frontmatter: true,
|
||||
gfm_autolink_literal: true,
|
||||
gfm_footnote_definition: true,
|
||||
gfm_label_start_footnote: true,
|
||||
gfm_strikethrough: true,
|
||||
gfm_table: true,
|
||||
gfm_task_list_item: true,
|
||||
hard_break_escape: true,
|
||||
hard_break_trailing: true,
|
||||
heading_atx: true,
|
||||
heading_setext: true,
|
||||
html_flow: true,
|
||||
html_text: true,
|
||||
label_start_image: true,
|
||||
label_start_link: true,
|
||||
label_end: true,
|
||||
list_item: true,
|
||||
math_flow: true,
|
||||
math_text: true,
|
||||
mdx_esm: false,
|
||||
mdx_expression_flow: false,
|
||||
mdx_expression_text: false,
|
||||
mdx_jsx_flow: false,
|
||||
mdx_jsx_text: false,
|
||||
thematic_break: true,
|
||||
},
|
||||
math_text_single_dollar: true,
|
||||
gfm_strikethrough_single_tilde: true,
|
||||
..Default::default()
|
||||
};
|
||||
let args = Args::parse();
|
||||
let mut ctx = Context::new(args);
|
||||
let mut input = String::new();
|
||||
|
||||
|
||||
let start = Instant::now();
|
||||
stdin().read_to_string(&mut input).unwrap();
|
||||
let stdin_read = start.elapsed();
|
||||
|
||||
let start = Instant::now();
|
||||
let ast = markdown::to_mdast(&input, &options).unwrap();
|
||||
let ast_parse = start.elapsed();
|
||||
|
||||
let start = Instant::now();
|
||||
ast.visit(&mut ctx);
|
||||
let ast_visit = start.elapsed();
|
||||
|
||||
if let Some(yaml) = &ctx.yaml {
|
||||
if let Some(val) = yaml.get("defaultLang") {
|
||||
ctx.default_lang = Some(val.as_str().unwrap().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let res = ast.to_html(&mut ctx);
|
||||
let html = finish(res);
|
||||
let html_convert = start.elapsed();
|
||||
|
||||
if ctx.args.timings {
|
||||
dbg!(stdin_read, ast_parse, ast_visit, html_convert, ctx.highlight_times, ctx.js_times, ctx.js_sum);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(yaml) = &mut ctx.yaml {
|
||||
yaml.insert("titles".to_string(), serde_json::to_value(&ctx.titles).unwrap());
|
||||
}
|
||||
|
||||
let value = ctx.script.clone().unwrap_or_else(|| String::from("<script></script>"));
|
||||
|
||||
let script = {
|
||||
let end = value.find('>').expect("Unclosed script tag (found <script but not >). May be a bug with Markdown parser.") + 1;
|
||||
let mut script = value[..end].to_string();
|
||||
let layout = ctx.resolve_layout();
|
||||
script += format!("import MDXLayout from \"{}\";", layout).as_str();
|
||||
script += &value[end..];
|
||||
script
|
||||
};
|
||||
|
||||
let frontmatter = (|| {
|
||||
serde_json::to_string(&ctx.yaml?).ok()
|
||||
})().unwrap_or("{}".to_string());
|
||||
println!("<script context=\"module\">export const metadata = {}</script>", frontmatter);
|
||||
|
||||
println!("{script}");
|
||||
println!("<MDXLayout {{...metadata}}>");
|
||||
println!("{}", html);
|
||||
println!("</MDXLayout>");
|
||||
}
|
||||
Loading…
Reference in a new issue