では機能があって、
マークダウンファイル内に指定の書式で書くだけで、コードブロックをページに表示させることができます。
特定の行をハイライト表示したり、行番号を非表示にしたりいろいろとできるのですが、
ファイル名を表示することができません。
そこで、JavaScript を使わずに Hugo のテンプレートを使って
コードブロックにファイル名を表示する方法をご紹介します。
完成イメージ
コードによってはファイル名が不要なものもあるので、切り替えができるようにします。
デフォルト状態では左図のようになっているので、
ファイル名を指定したときにのみ行番号の列とコードの列の位置を下に少しずらすか、コードブロックの天井を高くすることになります。
手順
実装の手順としては次のようになります。
- ファイル名を識別させる方法を決める
- ファイル名をどの部分に挿入するか決める
- テンプレートファイル内で、ファイル名を識別させる
- テンプレートファイル内で、ファイル名を表示するタグを作り、HTML ファイル内に挿入する
- CSS ファイルで見た目を調整する
ファイル名を識別させる方法を決める
思いつく方法としては2つあります。
①言語名の直後にファイル名をくっつける
例えば、マークダウンファイルに次のように書くと、(例は公式ドキュメントより)
```go {linenos=table,hl_lines=[8,"15-17"],linenostart=199}// ... code```
HTML の該当のコードブロックには次のような構造が出来上がります。
1<div class="highlight">2<div class="chroma">3<table class="lntable">4<tbody>5<tr>6<td class="lntd">7<pre class="chroma">8<code>9<span class="lnt">199</span>10</code>11</pre>12</td>13<td class="lntd">14<pre class="chroma">15<code class="language-go" data-lang="go">16<span class="c1">// ... code</span>17</code>18</pre>19</td>20</tr>21</tbody>22</table>23</div>24</div>
言語名の直後にファイル名を次のように書いてもエラーは発生せず、シンタックスハイライトも(多くの場合は)正常に動作します。
(ファイル名の拡張子の表す言語と言語名の表す言語が一致しない場合には、拡張子が優先されることがあるようです。)
```go:foo.go {linenos=table,hl_lines=[8,"15-17"],linenostart=199}// ... code```
このときの HTML の構造は次のようになります。
1<div class="highlight">2<div class="chroma">3<table class="lntable">4<tbody>5<tr>6<td class="lntd">7<pre class="chroma">8<code>9<span class="lnt">199</span>10</code>11</pre>12</td>13<td class="lntd">14<pre class="chroma">15<code class="language-go:foo.go" data-lang="go:foo.go">16<span class="c1">// ... code</span>17</code>18</pre>19</td>20</tr>21</tbody>22</table>23</div>24</div>
ここに明示的に出現するfoo.go
を HTML から探し出して取り出すという方法です。
②{ }の中に独自の記述を追加する
```go {linenos=table,hl_lines=[8,"15-17"],linenostart=199}// ... code```
の1行目の{}
の中では、行番号の表示方法やハイライトする行、行番号の始めの数字など、
そのコードブロックに関する情報を指定できます。
この書式に合わせる形で、
```go {name="foo.go",linenos=table,hl_lines=[8,"15-17"],linenostart=199}// ... code```
のように書き、マークダウンファイル内でその記述を探し出して取り出すという方法です。
ファイル名をどの部分に挿入するか決める
CSS 次第ですが、おそらく選択肢としては次のうちのどこかになるでしょう。
1<div class="highlight">2<div class="chroma">3<div class="code-name">foo.go</div>4<table class="lntable">5<tbody>6<tr>7<td class="lntd">8<div class="code-name">foo.go</div>9<pre class="chroma">10<div class="code-name">foo.go</div>11<code>12<span class="lnt">199</span>13</code>14</pre>15</td>16<td class="lntd">17<div class="code-name">foo.go</div>18<pre class="chroma">19<div class="code-name">foo.go</div>20<code class="language-go:foo.go" data-lang="go:foo.go">21<span class="c1">// ... code</span>22</code>23</pre>24</td>25</tr>26</tbody>27</table>28</div>29</div>
最初のもの以外は、新たに置いた要素が行番号かコード本文のどちらか片方を動かすことはあっても、両方を動かすことはありません。
そのため3行目に置いたほうが楽だと思います。
ここではこの位置にタグを挿入していきます。
テンプレートファイル内で、ファイル名を識別させる
ファイル名の識別方法でやり方が変わってきます。
①の場合
生成された HTML (.Content
。テーマで独自に変更が加えられている場合は.Scratch.Get "Content"
など)の中から
正規表現で特定のタグを探し出し、ファイル名を取り出します。
1{{- /* 正規表現で <div class="chroma">...</div> をさがす */ -}}2{{- $div_pattern := `<div class="chroma">(?:.|\n)*?</div>` -}}3{{- $div_chromas := (findRE $div_pattern (.Scratch.Get "Content")) -}}45{{- range $div_chromas -}}6{{- /* 正規表現で <div class="chroma">...<code class="language-...:..." ...> をさがす */ -}}7{{- $pattern := `<div class="chroma">(?:.|\n)*?<code class="language-.+?:(.+?)" data-lang=".+?:.+?">(?:.|\n)*?</div>` -}}8{{- if findRE $pattern . -}}9{{- /* ファイル名を取り出す */ -}}10{{- $file_name := . | replaceRE $pattern `$1` -}}11...12{{- end -}}13{{- end -}}
こちらは読み込むのも書き換えるのも HTML の同じ部分なので、比較的簡潔に書けます。
②の場合
マークダウンファイル(.RawContent
)の中から
正規表現で{..., name="...", ...}
となっている部分を探しだし、ファイル名を取り出します。
1{{- /* ローカルスクラッチ生成 */ -}}2{{- $scratch := newScratch -}}34{{- /* 正規表現でマークダウンファイルからコードブロックをさがす */ -}}5{{- $pattern_code_block := `\x60{3}\w+?(?:.|\n)*?\x60{3}\n` -}}6{{- $code_blocks := findRE $pattern_code_block .RawContent -}}78{{- /* 正規表現でコードブロックから */9/* name が指定されているコードブロックをさがす */ -}}10{{- $pattern_name := `^\x60{3}.+?(?:{|,)(?: +?|\n+?)?name="(.+?)".*?}(?:.|\n)*` -}}11{{- $pattern_id := `^\x60{3}.+?(?:{|,)(?: +?|\n+?)?id=(\d+?).*?}(?:.|\n)*` -}}12{{- $scratch.Set "pattern_name" $pattern_name -}}13{{- $scratch.Set "pattern_id" $pattern_id -}}14{{- range $index, $code_block := $code_blocks -}}15{{- $pattern_name := $scratch.Get "pattern_name" -}}16{{- $pattern_id := $scratch.Get "pattern_id" -}}1718{{- /* ファイル名 */ -}}19{{- $name := $code_block | replaceRE $pattern_name `$1` -}}20{{- /* 何番目のコードか */ -}}21{{- $id := $code_block | replaceRE $pattern_id `$1` -}}2223{{- /* ファイル名の指定があるとき */ -}}24{{- if (findRE $pattern_name $code_block) -}}25{{- $scratch.Add "name" (slice $name) -}}26{{- /* 何番目のコードか指定があるとき */ -}}27{{- if (findRE $pattern_id $code_block) -}}28{{- $scratch.Add "ids" (slice (int $id)) -}}29{{- else -}}30{{- /* 何番目のコードか指定がなかったとき */31/* コードブロックが上から何番目かの情報を使う */ -}}32{{- $scratch.Add "ids" (slice $index) -}}33{{- end -}}34{{- end -}}35{{- end -}}
\x60
というのはバッククォート(`)のことです。
読み込むのはマークダウンファイルですが、書き換えるのは HTML なので、
何番目のコードを書き換えるべきかの情報を伝達しなければなりません。
すべて手動でid
を指定する({..., id=4}などと常に指定する)こともできますが、こちらはある程度は自分で判断してくれるようなコードになっています。
ただおかしな挙動をしたときにも制御できるように手動で指定することもできます。
テンプレートファイル内で、
ファイル名を表示するタグを作り、
HTML ファイル内に挿入する
HTML の該当部分を挿入後のものに置き換えていきます。
①の場合
1{{- /* 正規表現で <div class="chroma">...</div> をさがす */ -}}2{{- $div_pattern := `<div class="chroma">(?:.|\n)*?</div>` -}}3{{- $div_chromas := (findRE $div_pattern (.Scratch.Get "Content")) -}}45{{- range $div_chromas -}}6{{- /* 正規表現で <div class="chroma">...<code class="language-...:..." ...> をさがす */ -}}7{{- $pattern := `<div class="chroma">(?:.|\n)*?<code class="language-.+?:(.+?)" data-lang=".+?:.+?">(?:.|\n)*?</div>` -}}8{{- $pattern := `<div class="chroma">((?:.|\n)*?<code class="language-.+?:(.+?)" data-lang=".+?:.+?">(?:.|\n)*?</div>)` -}}9{{- if findRE $pattern . -}}10{{- /* ファイル名を取り出す */ -}}11{{- $file_name := . | replaceRE $pattern `$1` -}}12{{- $file_name := . | replaceRE $pattern `$2` -}}13{{- $rest := . | replaceRE $pattern `$1` -}}1415{{- /* ファイル名を示す div タグを作成 */ -}}16{{- $div_name := printf `<div class="code-name">%s</div>` $file_name -}}1718{{- /* <div class="chroma"> の直後に <div class="code-name"></div> を挿入 */ -}}19{{- $new_div := printf `<div class="chroma">%s%s` $div_name $rest -}}20{{- $Content := replace ($.Scratch.Get "Content") . $new_div | safeHTML -}}21{{- $.Scratch.Set "Content" $Content -}}22{{- end -}}23{{- end -}}
正確な置き換えをするために、置き換えられる文を広めの範囲で判定していますが、
書き換えているのはほんの一部です。
変わっていない部分$rest
も置き換える文に入れ込むことに注意です。
②の場合
HTML から書き換える部分を探し出し、マークダウンファイルから得た情報を使って
新しいタグを作成して挿入しています。
1...2{{- /* 正規表現で HTML から */3/* <div class="chroma">...</div> をさがす */ -}}4{{- $pattern_chroma := `<div class="chroma">((?:.|\n)*?</div>)` -}}5{{- $divs := findRE $pattern_chroma (.Scratch.Get "Content") -}}6{{- $scratch.Set "divs" $divs -}}78{{- $names := $scratch.Get "name" -}}9{{- range $index, $name := $names -}}10{{- $id := index ($scratch.Get "ids") $index -}}1112{{- /* ファイル名を示す div タグを作成 */ -}}13{{- $div_name := printf `<div class="code-name">%s</div>` $name -}}1415{{- /* 変更する div タグ */ -}}16{{- $div := index ($scratch.Get "divs") $id -}}17{{- $rest := replace $div `<div class="chroma">` `` -}}1819{{- /* <div class="chroma"> の直後に <div class="code-name"></div> を挿入 */ -}}20{{- $new_div := printf `<div class="chroma">%s%s` $div_name $rest -}}21{{- $Content := replace ($.Scratch.Get "Content") $div $new_div | safeHTML -}}22{{- $.Scratch.Set "Content" $Content -}}23{{- end -}}
後半は①の場合とほぼ同じですが、
ハイライトで示した部分(16行目)でマークダウンファイルから取得した情報を HTML 要素の抽出に使用しています。
CSS ファイルで見た目を調整する
あとは見た目の部分をお好みで調整するだけです。
例としてコードを挙げておきます。
1.code-name {2display: inline; /* 表示形式 */3position: relative; /* 配置方法 */4top: 0.5em; /* 位置調整 */5left: 1em; /* 位置調整 */6border-bottom: solid thin #888888; /* 下線を引く */7}
以上でコードブロックにファイル名を表示することができるようになりました。
お疲れ様でした。
参考になれば幸いです。
では