おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

はじめてのRake(4)

「はじめてのRake」の第4回です。 今回はRakeの応用です。 Pandocを使ってマークダウンからHTMLを作る例を紹介します。

はじめてこのブログを見る方は「はじめてのRake(1)」からご覧になってください。

文中に[R]という記号で始まる段落は、「Ruby上級者向けの解説」です。 上級とは、ほぼ「クラスを記述できるレベル」を指します。 上級以外の方はこの部分を飛ばしてください。

Pandoc

まず、Pandocがどのようなアプリケーションなのかを説明します。 Pandocは、文書の形式を変換するアプリケーションです。 例えば、

  • Wordの文書をHTML文書にする
  • Markdownの文書をPDF文書にする

これ以外にも多数の文書形式がサポートされています。 詳しくはPandocのウェブサイトをご覧ください。

例としてexample.docxというワードファイルをHTMLにしてみましょう。 ワードファイルはこんな感じです。

example.docx

$ pandoc -so example.html example.docx

これにより、example.htmlというファイルができます。 ダブルクリックするとブラウザで内容が表示されます。

example.html

画面の見栄えはともかく、ワードで書いた内容がHTMLとして表示されていることが確認できるでしょう。

では、どのようなHTMLが生成されたのでしょうか。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <title>example</title>
  <style>
    code{white-space: pre-wrap;}
    span.smallcaps{font-variant: small-caps;}
    span.underline{text-decoration: underline;}
    div.column{display: inline-block; vertical-align: top; width: 50%;}
    div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
    ul.task-list{list-style: none;}
  </style>
</head>
<body>
<h2 id="pandocのインストール"><strong>P</strong>andocのインストール</h2>
<p>以下ではUbuntu22.04でのインストールを説明する。</p>
<p>端末から、apt-getを使ってインストールする。</p>
<p>$ sudo apt-get install pandoc</p>
<p>これでインストールできるPandocはたいていの場合、最新版ではない。Pandocの最新版は、
そのホームページからダウンロードできる。インストーラがあるので、それを用いるのが簡単である。</p>
<h2 id="rubyのインストール">Rubyのインストール</h2>
<p>端末から、apt-getを使ってインストールする。</p>
<p>$ sudo apt-get install ruby</p>
<p>最新版のRubyをインストールにはrbenvが良いが、rbenvをマスターするには時間がかかる。
詳しくは<a href="https://github.com/rbenv/rbenv">rbenv</a><a href="https://github.com/rbenv/ruby-build">ruby-build</a>のウェブサイトを参照してほしい。</p>
</body>
</html>

HTMLのソースコードから分かることで最も重要なことは、ヘッダが追加されていることです。 これはpandocに-sオプションをつけたからです。 -sをつけなければ、bodyタグで挟まれた本文の部分だけが生成されます。

マークダウンをHTMLに変換する

ここからは、マークダウンをHTMLに変換し、さらにRakeで作業を自動化する方法を学びます。

ソースファイルはすべてカレントディレクトリにあるとします。 生成するHTMLはdocsディレクトリに作成します。 マークダウンファイルは、「sec1.md」「sec2.md」「sec3.md」「sec4.md」ですが、将来ファイルが増えても対応できるようにRakefileを作ります。

※ 「sec1.md」は「はじめてのRake(1)」の編集画面に書き込んだマークダウンファイルに手を加えたものです。 「sec2.md」「sec3.md」「sec4.md」も同様に「はじめてのRake」のマークダウンファイルです。 これらのソースコードとほぼ同じものがGithubのRake-tutorial-for-beginners-jpレポジトリにアップロードしてあります。 Rakefileを試すときにはそこからダウンロードしてください。 RakefileのコードはGithubとブログでは若干違います。 そのため、試すときはダウンロードした方のRakefileで試してください。 また、できあがったHTMLがGithub Pagesで確認できます。

Pandocでは、最初に%とともにメタデータを書きます。 これは、タイトル、著者、日付を表します。

% はじめてのRake
% ToshioCP
% 2022/7/25

タイトルはHTMLのヘッダのタイトル・タグの内容にもなります。

PandocでHTMLにすると画面全面を使うので、横幅のあるPCでは広がりすぎて読みにくくなります。 それを解消するために、次のCSSファイル「style.css」を用意しました。

body {
  padding-right: 0.75rem;
  padding-left: 0.75rem;
  margin-right: auto;
  margin-left: auto;
}

@media (min-width: 576px) {
  body {
    max-width: 540px;
  }
}
@media (min-width: 768px) {
  body {
    max-width: 720px;
  }
}
@media (min-width: 992px) {
  body {
    max-width: 960px;
  }
}
@media (min-width: 1200px) {
  body {
    max-width: 1140px;
  }
}
@media (min-width: 1400px) {
  body {
    max-width: 1320px;
  }
}

このCSSはBootstrapのcontainerクラスの定義を参考に作りました。 CSSの内容説明は省略します。

これをstyle.cssという名前のファイルにしてRakefileのあるディレクトリに保存します。

Pandocで-cオプションを使うと、生成されたHTMLのヘッダでstyle.cssを取り込むようになります。

Rakefileの作成

それでは、「sec1.md」から「sec4.md」までの4つのファイルからHTMLファイルを作るRakefileを作ってみましょう。 ここで、2つの考え方があります。

  • sec1からsec4までを別々のHTMLファイルにし、それらをリンクでつなぐ。 目次を含むトップページはそれらとは別に作る
  • sec1.mdからsec4.mdまでを一つのファイルにつなげ、それをHTMLにする。

どちらにも一長一短があります。 ここでは、作成の簡単な2番目の方法を採用しましょう。

sources = FileList["sec*.md"]

task default: %w[docs/はじめてのRake.html docs/style.css]

file "docs/はじめてのRake.html" => %w[はじめてのRake.md docs] do |t|
  sh "pandoc -s --toc -c style.css -o #{t.name} #{t.source}"
end

file "はじめてのRake.md" => sources do |t|
  firstrake = t.sources.inject("") {|s1, s2| s1 << File.read(s2) + "\n"}
  File.write("はじめてのRake.md", firstrake)
end

file "docs/style.css" => %w[style.css docs] do |t|
  cp t.source, t.name
end

directory "docs"

少しタスクの間の関連が複雑になっています。

  • デフォルトのタスク「default」の事前タスクは「docs/はじめてのRake.html」と「docs/style.css」です
  • 「docs/はじめてのRake.html」はマークダウン「はじめてのRake.md」とディレクトリ「docs」に依存しています
  • 「はじめてのRake.md」は4つのファイル(「sec1.md」から「sec4.md」)に依存しています
  • 「docs/style.css」はそのコピー元の「style.css」とディレクトリ「docs」に依存しています
  • 「docs」はディレクトリタスクで、directoryメソッドで定義されます

6行目のshは、Rubysystemメソッドと似ています。 引数を外部コマンドとして実行します。 6行目の場合、シェルを介してpandocを起動します。 shメソッドはRakeがFileUtilsに拡張したもので、オリジナルのFileUtilsにはありません。

Pandocの--tocオプションは目次を自動生成するオプションです。 デフォルトでは#から###までが目次になります。

10行目のinjectメソッドは畳み込みを行う、配列インスタンスのメソッドです。 引数を初期値として、配列の値を次々にs2に代入して計算し、結果を次のs1に代入します。 順を追って説明しましょう

  • 初期値は引数の空文字列""です。 それがブロックのs1に代入されます
  • s2には最初の配列の要素である「sec1.md」が代入され、ブロック本体のs1 << File.read(s2) + "\n"が実行されます。 これにより、s1には「sec1.mdの内容+改行」が代入され、それが次のブロックのs1に代入されます。
  • 2回目のブロック実行で、s1は「sec1.mdの内容+改行」、s2には次の配列要素の「sec2.md」が代入されます。 ブロック本体が実行され、「s1」には「sec2.mdの内容+改行」が追加されます。 その結果、s1は「se1.mdの内容+改行+sec2.mdの内容+改行」となります。 これが次のs1に代入されます。
  • 3回目のブロック実行で、s1には前回実行の結果、s2には次の配列要素の「sec3.md」が代入されます。 前と同様に「sec3.mdの内容+改行」が追加されます。
  • 4回目(最後)のブロック実行で、s1には前回実行の結果、s2には次の配列要素の「sec4.md」が代入されます。 前と同様に「sec4.mdの内容+改行」が追加されます。
  • 以上の結果、firstrakeには「sec1.mdの内容+改行+sec2.mdの内容+改行+sec3.mdの内容+改行+sec4.mdの内容+改行」が代入されます。 要するに、4つのファイルを改行を挟んで結合した文字列になります。 11行目でそれがファイル「はじめてのRake.md」として保存されます。

改行をファイルの末尾に足したのは、一般に「テキストファイルの末尾は改行がある場合とない場合がある」からです。 改行が無い場合に次のファイルを接続すると、2番めのファイルの先頭の文字が行頭に来ません。 すると、見出しの「#」が行頭からずれて見出しでなくなるということが起こりえます。 これを避けるために改行を足しているのです。

cleanとclobber

この処理において「はじてのRake.md」というファイルは中間ファイルです。 重要なのはソースファイルと結果ファイルだと考えれば、処理後に中間ファイルは削除したいと思うかもしれません。 そのような操作を行うのがcleanタスクです。 cleanタスクを使うには

  • rake/cleanをrequireする
  • 定数CLEANの指すファイルリスト・オブジェクト(それも「CLEAN」と呼ぶことにします)に中間ファイルを追加する。 ファイルリストには配列と同様のメソッドが備わっているので、<<またはappendpushメソッドで追加ができる。

また、結果ファイルも含めて全て生成ファイルを消去するタスクがclobberです。

  • clobberタスクは、CLEANに登録されたファイルを削除する
  • さらに、ファイルリストCLOBBERに登録されたファイルも削除する

以上を付け加えたRakefileは次のようになります。

require 'rake/clean'

sources = FileList["sec*.md"]

task default: %w[docs/はじめてのRake.html docs/style.css]

file "docs/はじめてのRake.html" => %w[はじめてのRake.md docs] do |t|
  sh "pandoc -s --toc -c style.css -o #{t.name} #{t.source}"
end
CLEAN << "はじめてのRake.md"

file "はじめてのRake.md" => sources do |t|
  firstrake = t.sources.inject("") {|s1, s2| s1 << File.read(s2) + "\n"}
  File.write("はじめてのRake.md", firstrake)
end

file "docs/style.css" => %w[style.css docs] do |t|
  cp t.source, t.name
end

directory "docs"
CLOBBER << "docs"

中間ファイルを削除するには

$ rake clean

生成ファイル全てを削除するには

$ rake clobber

とします。

以上で4回にわたった「はじめてのRake」を終わります。 ここでは基本的な事項に限定して説明しましたが、それだけでもRakefileを書くのに十分な知識は得られたと思います。 このブログで触れなかった事項には、

  • タスクをマルチスレッドで実行し、高速化するためのmultitaskメソッド
  • Rakefileをカレントディレクトリ以外に置くこと、複数のRakefileを使うこと
  • タスクの名前空間
  • タスクの説明をするためのdescribeメソッド
  • rakeコマンドのオプション、とくにトレースなどのデバッグに使えるもの

などがあります。 今後はこれらの学習をしていくと良いと思います。

また、Rakeの機能を学習するだけでなく、ビルドをどのように構成するかについての学習も大事です。 これには経験を重ねることが一番大事だと思います。