This is a reStructuredText parser and HTML/Markdown generator implemented in pure TypeScript.
import fs from 'node:fs'
import { RstToHtmlCompiler } from 'rst-compiler'
const rst = fs.readFileSync('./index.rst').toString('utf-8')
const html = new RstToHtmlCompiler().compile(rst)
console.log(html.header) // Content that should be written to <head>
console.log(html.body) // Main document
In order to resolve references that may exist in other documents, the compiler uses two-stage compilation:
const compiler = new RstToHtmlCompiler()
// 1. The compiler first needs to parse each document into their respective ASTs
// References are NOT checked at this stage since the compiler does not know what other documents you may have
const doc1ParserOutput = compiler.parse(':doc:`See Document 2 <./foo>`')
const doc2ParserOutput = compiler.parse('Document 2')
// 2. The compiler then reads all the ASTs for your project in order to resolve references and generate HTML
const doc1Html = compiler.generate({
basePath: '/blog/', // Any links to other documents will be prefixed with this value
currentDocPath: 'index', // Relative to basePath
docs: [
{
docPath: 'index',
parserOutput: doc1ParserOutput,
},
{
docPath: 'foo',
parserOutput: doc2ParserOutput,
},
],
})
console.log(doc1Html.body) // <a href="/blog/foo">See Document 2</a>
Since reStructuredText does not have a formal specification, this program's behavior may deviate slightly from the reference implementation in Python's docutils library due to differences in parsing strategies.
-
Only spaces can be used for indentation
-
Third-party extensions and most directives and interpreted text roles from Sphinx are not available since they are implemented in Python
-
It is not recommended to run the compiler on untrusted input as there is no XSS sanitation on the output. In addition, the compiler makes extensive use of lookahead and lookbehind regular expressions which may cause ReDoS attacks.
If you wish to add additional functionality, you should either implement custom Directives (for block-level output) or custom InterpretedTexts (for inline-level output).
import { RstCompilerPlugin } from 'rst-compiler'
const plugin: RstCompilerPlugin = {
onInstall: (compiler) => {
compiler.useDirectiveGenerator({
directives: [
'custom-directive',
],
generate: (generatorState, node) => {
generatorState.visitNodes(node.children)
},
})
},
}
const compiler = new RstToHtmlCompiler()
compiler.usePlugin(plugin)
All of the built-in Directives and InterpretedText in the compiler are implemented as plugins as well. Please see the Plugins directory for example code.
All standard text roles are supported. In addition, the following text roles from Sphinx are supported:
See sphinx for more information.
See sphinx for more information.
See :download:`this example script <example.py>`.
const { downloads } = RstToHtmlCompiler.compile(rst)
console.log(downloads[0])
// {
// srcPath: '/example.py',
// destPath: '/_downloads/954c614a01144f97fd09b9c1d1dbc574fc10e6c0/example.py',
// }
The output will be an <a>
that links to the _downloads
directory.
You will need to serve it with appropriate Content-Type
headers that allow browsers to download the link.
See sphinx for more information.
.. danger::
Beware killer rabbits!
See docutils for more information.
.. image:: picture.png
See docutils for more information.
.. only:: html and draft
This text will only appear when html and draft fields in RstGeneratorOptions.outputEnv are true
RstToHtmlCompiler.compile(rst, {}, {
outputEnv: {
html: true,
draft: true,
},
})
See sphinx for more information.
code
, highlight
(powered by Shiki)
.. code:: python
def my_function():
"just a test"
print(8/2)
By default, the HTML compiler will simply output plaintext inside <pre>
tags.
If you wish to enable syntax highlighting, you will need to provide a shiki
object in RstGeneratorOptions
:
import { createHighlighter } from 'shiki'
import { RstToHtmlCompiler, RstGeneratorOptions } from 'rst-compiler'
const generatorOptions: Partial<RstGeneratorOptions> = {
shiki: {
theme: 'github-light',
highlighter: await createHighlighter({
langs: ['python', 'js'],
themes: ['github-light'],
}),
},
}
RstToHtmlCompiler.compile(rst, {}, generatorOptions)
See docutils for more information.
math
(powered by KaTeX)
.. math::
\sin(x) = \sum_{n=0}^{\infty} \frac{(-1)^n x^{2n+1}}{(2n+1)!}
const html = RstToHtmlCompiler.compile(rst)
console.log(html.header) // Will contain <link> to katex's css
See docutils for more information.
tabs
, tab
, code-tab
(based on sphinx-tabs
)
.. tabs::
.. tab:: Label A
Text A
.. tab:: Label B
Text B
const html = RstToHtmlCompiler.compile(rst)
console.log(html.header) // Will contain <script> needed to make tabs interactive
See sphinx-tabs for more information.
Note: If you are using the Markdown output in VitePress, you will need to install vitepress-plugin-tabs
Create ./tests/playground/playground.rst
(not tracked by Git) and write test input
-
Run
yarn playPy
to rundocutils
(Python reference implementation) on test input -
Run
yarn playJs
to runrst-compiler
on test input (requires Bun runtime)-
Press
F5
in VSCode to run this command inside the debugger -
Run
yarn server
to serve the output at localhost:8080
-