VSCode/ATOMのMarkdown-Preview-EnhancedのHTML変換をコマンドラインから行う

VSCodeやAtomのMarkdown-Preview-Enhanced(MPE)では、markdownファイルのプレビュー画面を右クリックするとhtmlファイルやPDFファイルに変換が可能だが大量のmarkdownファイルがあるときにいちいちプレビュー画面を開き変換ボタンを押すのが面倒だったので、ターミナルのコマンドラインから変換する方法がないかを調べたらあったのでそのやり方のメモ。

※プレビュー画面を開いたときにhtmlファイルを出力する方法もあるがやはり面倒である。

pandocを使いコマンドラインから変換を行うという選択肢も当然あったがhtmlの見た目が個人的にMPEの変換でできるものが好みだったのと`@import`によるファイルの展開などMPEの拡張機能をそのまま反映させたかったのでpandocは使わないことにした。MPEの内部で使われている変換ツール(mume)を同じ作者が公開しているのでそれを利用した。オプションなどは基本的にVSCode/ATOMのMPEの設定画面と同じなので簡単に同じ出力を実現できる。

- https://github.com/shd101wyy/mume
- https://www.npmjs.com/package/@shd101wyy/mume

## 方法

以下のコマンドラインはArch Linux上で行ってるが他のLinuxやMacでも基本は変わらないはず。

```sh
$ mkdir Node && cd Node # Node.jsを使用するため適当なディレクトリをつくる
$ npm init -y           # 初期化
$ npm install --save @shd101wyy/mume  # node_modules/@shd101wyy/mume ができる
$ export NODE_PATH=${HOME}/Node/node_modules # NODE_PATHを設定。.zprofileなどに書いとく
```

次にjsファイルを作る。オプションの詳細は上のリンクのgithubのREADMEに書いてあるがVSCodeやATOMでのMPEの設定画面と基本的に同じ。

```javascript:md2html.js
// node.js
const mume = require("@shd101wyy/mume");

const config = {
  // Enable this option will render markdown by pandoc instead of markdown-it.
  usePandocParser: false,

  // In Markdown, a single newline character doesn't cause a line break in the generated HTML. In GitHub Flavored Markdown, that is not true. Enable this config option to insert line breaks in rendered HTML for single newlines in Markdown source.
  breakOnSingleNewLine: true,

  // Enable smartypants and other sweet transforms.
  enableTypographer: false,

  // Enable conversion of URL-like text to links in the markdown preview.
  enableLinkify: true,

  // Math
  mathRenderingOption: "KaTeX",  // "KaTeX" | "MathJax" | "None"
  mathInlineDelimiters: [["$", "$"], ["\\(", "\\)"]],
  mathBlockDelimiters: [["$$", "$$"], ["\\[", "\\]"]],
  mathRenderingOnLineService: "https://latex.codecogs.com/gif.latex", // "https://latex.codecogs.com/svg.latex", "https://latex.codecogs.com/png.latex"

  // Enable Wiki Link syntax support. More information can be found a  https://help.github.com/articles/adding-links-to-wikis/
  enableWikiLinkSyntax: true,
  // By default, the extension for wikilink is `.md`. For example: [[test]] will direct to file path `test.md`.
  wikiLinkFileExtension: '.md',

  // Enable emoji & font-awesome plugin. This only works for markdown-it parser, but not pandoc parser.
  enableEmojiSyntax: true,

  // Enable extended table syntax to support merging table cells.
  enableExtendedTableSyntax: false,

  // Enable CriticMarkup syntax. Only works with markdown-it parser.
  // Please check http://criticmarkup.com/users-guide.php for more information.
  enableCriticMarkupSyntax: false,

  // Front matter rendering option
  frontMatterRenderingOption: 'none', // 'none' | 'table' | 'code block'

  // Mermaid theme
  mermaidTheme: 'mermaid.css', // 'mermaid.css' | 'mermaid.dark.css' | 'mermaid.forest.css'

  // Code Block theme
  // If `auto.css` is chosen, then the code block theme that best matches the current preview theme will be picked.
  codeBlockTheme: 'github.css',
  //  "auto.css",
  //  "default.css",
  //  "atom-dark.css",
  //  "atom-light.css",
  //  "atom-material.css",
  //  "coy.css",
  //  "darcula.css",
  //  "dark.css",
  //  "funky.css",
  //  "github.css",
  //  "hopscotch.css",
  //  "monokai.css",
  //  "okaidia.css",
  //  "one-dark.css",
  //  "one-light.css",
  //  "pen-paper-coffee.css",
  //  "pojoaque.css",
  //  "solarized-dark.css",
  //  "solarized-light.css",
  //  "twilight.css",
  //  "vue.css",
  //  "vs.css",
  //  "xonokai.css"

  // Preview theme
  previewTheme: 'github-dark.css',
  // "atom-dark.css",
  // "atom-light.css",
  // "atom-material.css",
  // "github-dark.css",
  // "github-light.css",
  // "gothic.css",
  // "medium.css",
  // "monokai.css",
  // "newsprint.css",
  // "night.css",
  // "none.css",
  // "one-dark.css",
  // "one-light.css",
  // "solarized-dark.css",
  // "solarized-light.css",
  // "vue.css"

  // Revealjs presentation theme
  revealjsTheme: "white.css",
  // "beige.css",
  // "black.css",
  // "blood.css",
  // "league.css",
  // "moon.css",
  // "night.css",
  // "serif.css",
  // "simple.css",
  // "sky.css",
  // "solarized.css",
  // "white.css",
  // "none.css"

  // Accepted protocols for links.
 protocolsWhiteList: "http://, https://, atom://, file://, mailto:, tel:",

  // When using Image Helper to copy images, by default images will be copied to root image folder path '/assets'
  imageFolderPath: '/assets',

  // Whether to print background for file export or not. If set to `false`, then `github-light` preview theme will b  used. You can also set `print_background` in front-matter for individual files.
  printBackground: true,

  // Chrome executable path, which is used for Puppeteer export. Leaving it empty means the path will be found automatically.
  chromePath: '',

  // ImageMagick command line path. Should be either `magick` or `convert`. Leaving it empty means the path will be found automatically.
  imageMagickPath: '',

  // Pandoc executable path
  pandocPath: 'pandoc',

  // Pandoc markdown flavor
  pandocMarkdownFlavor: "markdown-raw_tex+tex_math_single_backslash",

  // Pandoc arguments e.g. ['--smart', '--filter=/bin/exe']. Please use long argument names.
  pandocArguments: [],

  // Default latex engine for Pandoc export and latex code chunk.
  latexEngine: 'pdflatex',

  // Enables executing code chunks and importing javascript files.
  // ⚠ ️ Please use this feature with caution because it may put your security at risk!
  //    Your machine can get hacked if someone makes you open a markdown with malicious code while script execution is enabled.
  enableScriptExecution: true,
 // Enables transform audio video link to to html5 embed audio video tags.
  // Internally it enables markdown-it-html5-embed plugins.
  enableHTML5Embed: false,

  // Enables video/audio embed with ![]() syntax (default).
  HTML5EmbedUseImageSyntax: true,

  // Enables video/audio embed with []() syntax.
  HTML5EmbedUseLinkSyntax: false,

  // When true embed media with http:// schema in URLs. When false ignore and don't embed them.
  HTML5EmbedIsAllowedHttp: false,

  // HTML attributes to pass to audio tags.
  HTML5EmbedAudioAttributes: 'controls preload="metadata" width="320"',

  // HTML attributes to pass to video tags.
  HTML5EmbedVideoAttributes: 'controls preload="metadata" width="320" height="240"',

  // Puppeteer waits for a certain timeout in milliseconds before the document export.
  puppeteerWaitForTimeout: 0,

  // If set to true, then locally installed puppeteer-core will be required. Otherwise, the puppeteer globally 
  installed by `npm install -g puppeteer` will be required.
  usePuppeteerCore: true
}

async function main() {
  await mume.init();

  const engine = new mume.MarkdownEngine({
    filePath: process.argv[2],
    config: config
  });

  // open in browser
  //await engine.openInBrowser({ runAllCodeChunks: true });

  // html export
  await engine.htmlExport({ offline: false, runAllCodeChunks: true });

  return process.exit();
}

main();
```

READMEには必要なカンマがなかったりするので適当に修正する。PDFへの変換部分などは省いているのでPDFに変換したい場合はREADMEを参照。上記の設定ファイルでは第一引数にmarkdownファイルをとり、それを変換するようにしている。

## 使い方

```sh
$ node md2html.js hoge.md
```

これで`hoge.html`のように拡張子がhtmlになったファイルができる。これだけで完了なのだが、変換されたhtmlファイル内に別markdownファイルへのリンクがあれば拡張子が`.md`のままでクリックするとmarkdownファイルに飛んでしまうため、htmlファイル内の`.md`を`.html`に書き換えできるようにシェル関数を作る。

```zsh
function md2html() {
    input_md=${1}
    input_prefix=${input_md%.*}
    output_html=${input_prefix}.html

    nodejs_prog=${prog_path}/md2html.js # ${prog_path}は自分がファイルを置いている場所
    node ${nodejs_prog} ${input_md}

    # 拡張子が.md, .markdownを.htmlに書き換え
    if [[ -f ${output_html} ]]; then
       sed -i -e 's/\.md\"/\.html\"/g' ${output_html}
       sed -i -e 's/\.markdown\"/\.html\"/g' ${output_html}
    fi
}
```

このようなシェル関数を作れば

```sh
$ md2html hoge.md
```

とすることにより`home.html`ができ、内部のリンクもhtmlになってくれる。このようなコマンドラインから呼び出せる関数を作ってしまえば、`find`や`xargs`と組み合わせることにより大量のmarkdownファイルを一括変換するときに非常に便利になった。

### MPEのスタイルシート

Linuxの場合は `${HOME}/.mume/style.less` をいじることでスタイルシートの変更が可能(VSCodeでもATOMでも)。