なぜ
作ったツールをリリースする際に、必要なもの、それは使い方の説明書です。
最近は、Readme.mdを作成して、HTML化することが流行っていますね。
ただ、状況によっては、GitHubにソースをPushできなかったり、Readme.mdをHTML化する技術を持たない人にリリースしなくてはならないケースもあります。
そんなときに自動的にHTMLを作ってしまおうということで本記事を作成しました。
最終方針
前提として、受け取る人は、デフォルトのWindowsしか使えない一般人とします。
自力で、MarkdownをHTML化できる人なんかは、勝手にやってもらえばよいのです。
最終的に、ベースとなるhtmlの特定個所に別ファイルに記述したMarkdownをbatch処理でコピーする方針となりました。ベースのhtmlは、marked.jsのサンプルを一部改変して、以下としました。
marked
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Marked in the browser</title>
</head>
<body>
<div id="content"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
document.getElementById('content').innerHTML =
marked(' \
$include
');
</script>
</body>
</html>
$includeに別ファイルに記述したReadme.mdをコピーするだけです。
さて、バッチファイルは以下の通りです。
@echo off
chcp 65001
set OutputFile="Readme.html"
echo. > %OutputFile%
for /f "tokens=1* delims=: eol=" %%a in ('findstr /n "^" base.html') do (
if "%%b"=="$include" (
for /f "tokens=1* delims=: eol=" %%x in ('findstr /n "^" Readme.md') do (
(echo.%%y\n\) >> %OutputFile%
)
) else (
(echo.%%Y) >> %OutputFile%
)
)
なんじゃ、こりゃ。。。
ただ、文字列を置き換えるだけなのに大層な呪文になりました。
動作概略は以下の通りです。
- 入出力をUTF-8に指定
- 空のReadme.htmlを作成
- base.htmlを1行ずつ読み込み、読み込んだ行をReadme.htmlに追記
- $includeを検出するとReadme.mdを一行ずつ読み込み、改行前に\nと\を追加してReadme.htmlに追記
batchでファイルを読み込む際は、スペース区切りになってしまったり、空行が無視されたりするので、上記のような呪文になってしまいます。
詳細は、バッチファイル | テキストファイルを 1 行ずつ読み込む (完全版?)
ちなみにbashで書くと以下のようになります。
#!/bin/bash
IFS=$'\n'
OFILE="Readme.html"
cp /dev/null $OFILE
for line in `cat base.html`
do
if [[ $line == *\$include* ]]; then
perl -pe 's/\r\n|\r|\n/\\n\\\n/g' Readme.md >> $OFILE
else
echo $line >> $OFILE
fi
done
perlの正規表現置換を心置きなく使えるので、すっきりですね。
なぜ、こうなった!?
そもそも、jQueryでローカルファイルを読み込んでくればいいんでない?と思う方がいるかもしれません。
ええ、筆者もその一人でした。とても、よい記事があります。
Marked.js で Markdown をクライアント側でパースして表示する
コピペして使ってみましょう!
This content failed to load.
あれ、パス指定を間違えたかな?
ctrl+shift+iを押して、、、
Access to XMLHttpRequest at 'file:///C:/Users/qiita/Documents/Markdown/Readme.md' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
なんじゃこりゃ...
どうも、最新(2021/3時点)のブラウザでは、ローカルファイルをハードコーディングして指定してもアクセスできないようです。
【Ajax】ローカルファイルを読み込もうとしたらCORSエラーが発生したので解決した
ここに書いてある解決方法を一般のWindowsユーザーに試してもらうことなんて、当たり前ですが、できません。
また、ホストを別に準備できない場合もあります。
なので、1手間の面倒ですが、バッチファイルを実行してもらう形式にしたわけです。
ここで記事を終えるのも何なので、スタイルを整えたベースのhtmlを参考までに記載しておきます。
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>
Readme
</title>
</head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/2.0.1/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js"</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="css/mystyle.css" rel="stylesheet">
<body>
<div class="container">
<div class="main">
<div class="header-content">
<div id="content"> </div>
</div>
</div>
<div class="side">
<div id="headline-preview"> </div>
</div>
</div>
</body>
<script>
hljs.highlightAll();
function headline(markdown){
lines = markdown.split(/\r\n|\r|\n/);
var headline = {};
var in_code = false;
for(var it in lines){
var line = lines[it]
var top_c = line[0]
if(lines[it].match(/^```.?/)){
in_code = in_code ? false : true;
}
if(lines[it][0] == '#' && !in_code){
level = ( lines[it].match( /#/g, ) || [] ).length;
str = lines[it].replace(/^#+/, '').trim()
headline[str] = level;
}
}
var preview = "<ul>";
var cur = 1;
for(var key in headline){
var ldif = headline[key] - cur;
if (ldif > 0){
for(var i = 0; i < ldif; i++){
preview += "<ul>";
}
}
else if(ldif < 0){
for(var i = 0; i < -ldif; i++){
preview += "</ul>";
}
}
cur = headline[key];
name = key;
key = key.toLowerCase();
key = key.replace(' ', '-');
preview += "<li>"
+ '<a href="#'
+ key
+ '">'
+ name
+ "</a></li>";
}
preview += "</ul>";
document.getElementById('headline-preview').innerHTML = preview;
}
var markdown = '\
$include
';
let renderer = new marked.Renderer()
renderer.table = function(header, body) {
if (body) body = '<tbody>' + body + '</tbody>';
return '<table class="table table-striped">\n'
+ '<thead>\n'
+ header
+ '</thead>\n'
+ body
+ '</table>\n';
}
mhtml= marked(markdown, {renderer:renderer});
document.getElementById('content').innerHTML = mhtml;
headline(markdown);
</script>
</html>
img {
max-width: 100%;
}
.container {
margin: auto;
display: flex;
justify-content: space-between;
width: 1030px;
}
.main {
width: 640px;
}
.side {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
width: 300px;
height: 100%;
}
h1, h2, h3 {
margin-block-start: 1rem;
margin-block-end: 1rem;
margin-inline-start: 0px;
margin-inline-end: 0px;
font-size: 1.17em;
display: block;
font-weight: bold;
}
.header-content h1, .header-content h2, .header-content h3,
.header-content h4, .header-content h5, .header-content h6{
font-weight: bold;
line-height: 1.5;
font-feature-settings: "palt";
margin-top: 1.5em;
margin-bottom: 1.5em;
cursor: pointer;
position: relative;
}
.header-content h1{
font-size: 1.8em;
border-bottom: 2px solid #ddd;
padding-bottom: 0.2em;
}
.header-content h2{
font-size: 1.6em;
padding-bottom: 0.1em;
}