Ménneu
Component-based extensible document processor
You can easily build the complex documents written in Markdown, HTML and LSX
that including images, charts, UML diagrams, barcodes and 2d codes (QR Code).
And get the output as a PDF, PNG and JPEG rendered by Puppeteer, or the HTML that packed into the single file.
Furthermore, you can insert the data from the file into the document with the control statements.
Examples
Markdown Demo source / pdf | Billing Statement source / pdf | HTML Demo source / pdf |
Testing the basic and extended markdown syntaxes. | Reporting example that markuped up with Lisp LSX syntax. | Testing the html template that embedding Lisp LSX. |
Real world examples
-
mdne - Markdown Neo Edit
A simple markdown and code editor powered by Markdown-it, Ace and Carlo. -
Ménneu Reporting App for kintone
Create✨ beautiful✨ 📑 📊 reports📈 📰 easily with Ménneu + kintone.
You can easily build the complex documents written in Markdown, HTML and LSX that including🖼 images,📊 charts,🔷 UML diagrams, barcodes and 2d codes (QR Code).
Getting started
Use CLI:
install via NPM:
$ npm install -g menneu
and run Ménneu:
$ menneu README.md --raw -o README.pdf
Add shortcuts to Windows file explorer right-click 'Send to' menu
Prerequirements
$ npm install -g menneu
Install
Download the source archive from https://github.com/shellyln/menneu/archive/master.zip and extract it.
> cd menneu\shell-ext\windows
> make-sendto-shortcuts.cmd
Use APIs:
install via NPM:
$ npm install menneu --save
and import Ménneu in your code:
// index.mjs
import './extension'; // * To import without using webpack,
// use node with the
// `--experimental-modules --no-warnings` options.
// * If `node>=12`, `--es-module-specifier-resolution=node`
// option is additionally required.
import { render } from 'menneu/modules';
import fs from 'fs';
import util from 'util';
const writeFileAsync = util.promisify(fs.writeFile);
(async () => {
try {
const buf = await render('# Hello!', {}, {
rawInput: true,
inputFormat: 'md',
dataFormat: 'object',
outputFormat: 'pdf',
});
await writeFileAsync('./hello.pdf', buf);
} catch (e) {
console.log(e);
}
})();
// extension.js
const fs = require('fs');
require.extensions['.css'] = function (module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
};
NOTE: To build it, you should use
webpack
+raw-loader
(or other packagers and/or plugins) to load CSS as string.You can also import from the
.mjs
file on a node with the--experimental-modules --no-warnings
options enabled,
and importmenneu/modules/*
paths.If you run it on
node>=12
,--es-module-specifier-resolution=node
option is additionally required.
NOTICE:
Use withwebpack >= 5
If you get the error:
Module not found: Error: Can't resolve '(importing/path/to/filename)' in '(path/to/node_modules/path/to/dirname)' Did you mean '(filename).js'?`
Add following setting to your
webpack.config.js
.{ test: /\.m?js/, resolve: { fullySpecified: false, }, },On
webpack >= 5
, the extension in the request is mandatory for it to be fully specified if the origin is a '.mjs' file or a '.js' file where the package.json contains '"type": "module"'.
Use APIs on the browsers:
Install via NPM, or download UMD from release page.
If you wish to use UMD single file on browser, Please write as below:
- index.html
<!DOCTYPE html> <head><meta charset="UTF-8"> <script src="./menneu.min.js"></script><script> (async() => { try { const buf = await menneu.render('# Hello!', {}, { rawInput: true, inputFormat: 'md', dataFormat: 'object', outputFormat: 'html', }); console.log((new TextDecoder).decode(buf)); } catch (e) { console.log(e); } })(); </script></head>
If you wish to use UMD single file on Node.js w/o installing react, Please write as below:
- menneu-umd-bootstrap.js
// Usage: echo "# Hello" | node ./menneu-umd-bootstrap.js - -of html const Module = require('module'); const loader = Module._load; Module._load = (request, parent) => { if (request === 'react' || request === 'react-dom' || request === 'react-dom/server') { return ({}); } return loader(request, parent); }; const menneu = require('./menneu.min.js'); menneu.run();
Playground
https://shellyln.github.io/menneu/playground.html
Express starter with the browser
Ménneu Markdown Notebook
Edit markdown locally w/o installing any apps.
GUI Editor
mdne - Markdown Neo Edit
A simple markdown and code editor powered by Ace and Carlo.
CLI
menneu -h
menneu --help
menneu InputFilePath [OPTIONS]
menneu - [OPTIONS]
-
InputFilePath
- Path to input document template file.
- If
-
is set, read fromSTDIN
. - If
-i
or--in
is set in the OPTIONS, InputFilePath points a path of data file.
Options
-
-h
,--help
- Show this help.
-
-i
InFilePath,--in
InFilePath- InFilePath: Path to input document template file.
-
-if
InFormatName,--in-format
InFormatName-
InFormatName :
lsx
|lisp
|md
|markdown
|html
|htm
- Input document template file format.
- This format is set automatically from template file's extension.
- If it is not set, Defailt is
md
.
- If it is not set, Defailt is
-
InFormatName :
-
--raw
- Disable Lisp block expansion.
- This option can be set for
md
|markdown
|html
|htm
.
- This option can be set for
- Disable Lisp block expansion.
-
-c
ConfigJsonOrJsPath,--config
ConfigJsonOrJsPath-
ConfigJsonOrJsPath : Path to
menneu.config.js
|menneu.config.json
. - Default is
menneu.config.js
|menneu.config.json
that is in the same directory to input file.- If no
menneu.config.js
|menneu.config.json
files is in the same directory to input file, usemenneu.config.js
|menneu.config.json
in the current working directory.
- If no
-
ConfigJsonOrJsPath : Path to
-
-df
DataDormatName,--data-format
DataDormatName-
DataDormatName :
json
|lisp
- The file format of the data applied to the document template.
- This format is set automatically from data file's extension.
- If it is not set, defailt is
json
.
- If it is not set, defailt is
-
DataDormatName :
-
-d
DataPath- DataPath : Path to data file.
-
-of
OutFormatName,--out-format
OutFormatName-
OutFormatName :
html
|pdf
|png
|jpeg
- Output file format.
- This format is set automatically from output file's extension.
- If it is not set, defailt is
pdf
.
- If it is not set, defailt is
-
OutFormatName :
-
-o
OutPath,--out
OutPath- OutPath: Path to output file.
- If this option is not present, it is output to
STDOUT
.
-
-t
TempDir,--tmpdir
TempDir- TempDir: Path to temporary directory that to generate the temporary html file passing to the Puppeteer.
-
-ti
,--tmp-indir
- Set TempDir to the parent directory of the input document file.
- It is default option.
- Set TempDir to the parent directory of the input document file.
-
-tc
,--tmp-cwd
- Set TempDir to the current working directory.
-
-to
,--tmp-os
- Set TempDir to the system temporary directory.
-
-tm
,--tmp-mem
- No temporary directory is used. Pass a data URL to the Puppeteer.
-
--dark-theme
- Use dark theme to render markdown.
-
--watch
- Watch changes of the parent directory of
InputFilePath
forever. - If changes are detected, update the output.
- Watch changes of the parent directory of
Config file
.js
or .json
are available.
const escapeHtml = (s) => s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
module.exports = {
title: 'example', // Document title of markdown.
// bodyStyle: '', // <body> style of markdown.
markdownBodyStyle: // "markdownBody" <div> style of markdown.
'font-family: "Yu Gothic Medium", "Microsoft JhengHei", arial, sans-serif;',
// tocIncludeLevel: [1, 2], // Headings levels to use (2 for h2:s etc)
// https://github.com/Oktavilla/markdown-it-table-of-contents/blob/master/README.md#options
// rawInput: true, // Disable Lisp block expansion.
// inputFormat: 'md', // Input document template file format. (md | html | lsx)
// dataFormat: 'json', // The file format of the data applied to the document template. (json | lisp)
// outputFormat: 'pdf', // Output file format. (pdf | html | png | jpeg)
// darkTheme: true, // Use dark theme to render markdown.
// launchOptions: // Puppeteer's option. See "puppeteer.launch(options)".
// { headless: false }, // https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md#puppeteerlaunchoptions
// navigateOptions: {}, // Puppeteer's option. See "page.goto(url, options)".
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options
// imageOptions: {}, // Puppeteer's option. See "page.screenshot([options])".
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions
pdfOptions: { // Puppeteer's option. See "page.pdf(options)".
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions
width: '210mm',
height: '297mm',
printBackground: true,
landscape: false,
preferCSSPageSize: false,
displayHeaderFooter: true,
headerTemplate: `
<div style="margin: 0mm auto -10mm; text-align: center; font-size: 9pt;">
<span class="title"></span>
</div>`,
footerTemplate: `
<div style="margin: 10mm auto 0mm; text-align: center; font-size: 9pt;">
<span class="pageNumber"></span> of <span class="totalPages"></span>
</div>`,
},
globals: { // Lisp global variables.
"$now": () => (new Date).toLocaleDateString('en-US'),
"$to-locale-string": (...args) => args.slice(-1)[0].toLocaleString(...(args.slice(0, -1))),
"$dir": (...args) => console.dir(...args),
"qwerty": "asdfgh",
},
// noDefaultComponents: true, // Disable default components.
components: { // Additional RedAgate components.
// See also https://github.com/shellyln/red-agate/tree/master/packages/red-agate
Greeting: (props) => `Hello, ${props.to}! ${props.children}`,
},
// noDefaultMarkdownPlugins: // Disable default markdown-it plugins.
// true,
// markdownPlugins: // Additional markdown-it plugins.
// [{ plugin: require('markdown-it-'), options: [] }],
markdownCustomContainers: [{ // See https://github.com/markdown-it/markdown-it-container
name: 'content',
}, {
name: 'spoiler',
validate: (params) => {
return params.trim().match(/^spoiler\s+(.*)$/);
},
render: (tokens, idx) => {
const m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return '<details><summary>' + escapeHtml(m[1]) + '</summary>\n';
} else {
// closing tag
return '</details>\n';
}
},
}],
// replacementMacros: [{
// re: /\!\!\!([\s\S]+?)\!\!\!/g,
// fn: 'lsx', // evaluate input as LSX script
// }, {
// re: /\$\$\$\{(.)([\s\S]+?)\}\$\$\$/g,
// fn: async (m, p0, p1) =>
// `<span style="background-color: green;"><strong>${p0}</strong>${p1}</span>`,
// async: true,
// }, {
// re: /\$\{(.)([\s\S]+?)\}/g,
// fn: (m, p0, p1) =>
// `<span style="background-color: pink;"><strong>${p0}</strong>${p1}</span>`,
// }],
// plantUmlServerUrl: // markdown-it-plantuml server URL
// 'https://www.example.com/plantuml',
// tocIncludeLevel: // markdown-it-table-of-contents TOC levels
// [1, 2, 3],
};
You can also export configuration by using the function.
module.exports = (env) => {
// env is following object:
// {
// styles: {
// normalizeCss: string,
// markdownCss: string,
// markdownDarkCss: string,
// highlightCss: string,
// paperCss: string,
// },
// moment: object,
// Liyad: object,
// RedAgateUtil: object,
// RedAgateSvgCanvas: object,
// RedAgateMath: object,
// RedAgate: object,
// React: object,
// ReactDom: object,
// components: object,
// highlightJs: object,
// markdownit: object,
// markdownitPlugins: {
// markdownitContaier: object,
// markdownitEmoji: object,
// markdownitSub: object,
// markdownitSup: object,
// markdownitIns: object,
// markdownitMark: object,
// markdownitCheckbox: object,
// markdownitPlantuml: object,
// markdownitMath: object,
// markdownitImsize: object,
// markdownitAnchor: object,
// markdownitToc: object,
// markdownitFootnote: object,
// markdownitDeflist: object,
// markdownitAbbr: object,
// },
// getMarkdownIt: function,
// getMarkdownRoot: function,
// }
// The function should return the configuration object.
return {
...
};
};
Features
Render markdown into HTML and PDF.
Markdown is parsed into HTML by markdown-it and converting from HTML into PDF by puppeteer .
Following markdown-it plugins are available by default:
- markdown-it-anchor
- markdown-it-checkbox
- markdown-it-container
- markdown-it-emoji
- markdown-it-imsize
- markdown-it-math
- markdown-it-plantuml
- markdown-it-table-of-contents
- markdown-it-sub
- markdown-it-sup
- markdown-it-ins
- markdown-it-mark
- markdown-it-footnote
- markdown-it-deflist
- markdown-it-abbr
You can append other plugins by configureing the menneu.config.js
.
HTML source files are also available.
Render LSX template into HTML and PDF.
See Liyad for more informations about Lisp and LSX syntax and operators.
Lisp block expansion
In the markdown or HTML documents, you can start Lisp
block.
The block starts with %%%(
and ends with pair parenthesis )
.
- You should escape following characters in the document:
-
\
->\\
-
"""
->\"\"\"
-
%%%
->\%\%\%
-
Conditional branch
%%%($last ;; "$last" is a function that evaluate parameters, and returns last parameter.
($set ($data isMorning) false)
($set ($data name) "World")
nil ;; "nil" is zero length array. it will replace to zero length string by document processor.
)
%%%($=if ($get $data isMorning)
"""Markdown
## Good morning, %%%($get $data name)!
""")
%%%($=if ($not ($get $data isMorning))
"""Markdown
## Hello, %%%($get $data name)!
""")
is equivalent to
## Hello, World!
Repeating
# Greeting
%%%($=for ($list "World" "Jane" "Joe")
"""Markdown
## Hello, %%%($get $data)!
""")
Good morning!
is equivalent to
# Greeting
## Hello, World!
## Hello, Jane!
## Hello, Joe!
Good morning!
Variables
- The data file is parsed and set to
$data
variable.
Data file:
{
"foo": 1,
"bar": "World"
}
Document template:
## Hello, %%%($get $data bar)!
Result:
<h2>Hello, World!</h2>
- To define the variable, use
$let
function in the Lisp block.
Document template:
%%%($let a "A")
%%%($get a)
Result:
<p>A</p>
- To set the value to the variable, use
$set
function in the Lisp block.
Document template:
%%%($let a "A")
%%%($set a "B")
%%%($get a)
Result:
<p>B</p>
- To set the value to the object property or array index, you can also use
$set
function.
Document template:
%%%($let a (# ;; "#" is object literal function.
(foo 1)
(bar ($list "World" "Jane" "Joe")) ))
%%%($set (a bar 1) "John")
%%%($get a bar 1)
Result:
<p>John</p>
Functions
Document template:
%%%($last
($defun fac (n)
($if (== n 0)
1
(* n ($self (- n 1))) ) )
nil)
Factorial of 3 is %%%(fac 3).
Result:
<p>Factorial of 3 is 6.</p>
LSX DOM elements
You can markup standard HTML and SVG tags witten in LSX notation.
Document template:
%%%(style (@ (dangerouslySetInnerHTML ".content { font-style: italic; color: red; }")))
Result:
<style>.content { font-style: italic; color: red; }</style>
Components
You also can markup with RedAgate tag-lib components.
Document template:
%%%(Greeting (@ (to "Menneu")) "Good morning!")
%%%(Svg (@ (width 100)
(height 100)
(unit "mm") )
(Canvas (-> (ctx) (::ctx@moveTo 10 10)
(::ctx@lineTo 190 190)
(::ctx:strokeStyle="rgba(255,128,0,0.2)")
(::ctx@stroke)
(::ctx@beginPath) ))
(Rect (@ (x 5)
(y 67)
(width 70)
(height 11)
(strokeColor "blue")
(stroke) ))
(Qr (@ (x 5)
(y 7)
(cellSize 0.8)
(data "Hello") ))
(Code128(@ (x 35)
(y 7)
(elementWidth 0.66)
(height 15)
(quietHeight 0)
(textHeight 7)
(font "7px 'OCRB'")
(data "Hello") ))
(Gtin13 (@ (x 10)
(y 37)
(elementWidth 0.66)
(height 15)
(quietHeight 0)
(textHeight 7)
(font "7px 'OCRB'")
(data "123456789012") )) )
menneu.config.js
:
module.exports = {
...
components: {
Greeting: (props) => `Hello, ${props.to}! ${props.children}`,
},
...
};
Result:
<p>Hello, Menneu! Good morning!</p>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100mm" height="100mm" viewBox="0 0 100 100">
...
</svg>
Following components are available by default:
- Utilities
- Resource bundlers
- HTML and XML
- SVG and Canvas
- Printer marks
- Barcodes and 2D codes
- Markdown
- MarkdownRoot
-
Markdown
- This is using the markdown-it.
- HTML fragments
- Math ML
-
Math
- This is using the markdown-it-math.
- Mml
-
Math
- Charts and UML graphs
-
Chart
- This is using the Chart.js and chartjs-plugin-datalabels.
-
PlantUml
- This is using the markdown-it-plantuml.
- PlantUmlLite
-
Chart
- Style sheets
-
NormalizeCss
- Include a Normalize.css stylesheet into the document.
-
MarkdownCss
- Include a github-markdown-css stylesheet into the document.
-
HighlightCss
- Include a highlight.js stylesheet into the document.
-
PaperCss
- Include a paper-css stylesheet into the document.
-
NormalizeCss
APIs
render()
export async function render(source: string, data: any, options: RenderOptions): Promise<Buffer>;
Render the document from document template.
- returns : Buffer of output document.
-
source
: Document template. -
data
: Data (json string | lisp string | object) -
options
: Render options.
processDocument()
export async function processDocument(config: CliConfig): Promise<Buffer>;
Read input file or STDIN, read config file, render and output document into file or STDOUT.
- returns : Buffer of output document.
-
config
: Configurations that specified by command line options.
run()
export async function run();
Main of CLI app.
Parse command line options and call processDocument().
parameters types :
export interface MarkdownOptions {
noDefaultMarkdownPlugins?: boolean;
markdownPlugins?: Array<{
plugin: any,
options: any[],
}>;
markdownCustomContainers?: Array<{
name: string,
validate?: (params: string) => boolean,
render?: (tokens: any[], index: number) => string,
marker?: string,
}>;
}
export interface FormatOptions {
rawInput?: boolean;
inputFormat: 'markdown' | 'md' | 'html' | 'htm' | 'lsx' | 'lisp';
dataFormat: 'lisp' | 'json' | 'object';
outputFormat: 'html' | 'pdf' | 'png' | 'jpeg';
}
export interface RenderOptions extends MarkdownOptions, FormatOptions {
title?: string;
navigateOptions?: any;
imageOptions?: any;
pdfOptions?: any;
globals?: object;
noDefaultComponents?: boolean;
components?: object;
}
export interface CliConfig extends FormatOptions {
useStdin: boolean;
inputPath?: string;
configPath?: string;
configFormat: 'js' | 'json' | 'object';
dataPath?: string;
useStdout: boolean;
outputPath?: string;
watch?: boolean;
}
License
ISC
Copyright (c) 2018 - 2020 Shellyl_N and Authors.
Bundled softwares' license
- github-markdown-css: license (MIT)
- highlight.js: license (BSD 3-Clause)
- normalize.css: license (MIT)