CircleCIでmarkdownからスライド資料を自動生成する

先日初めてLTというものをする機会がありましたので、せっかくだし楽にスライド資料を作れる仕組みを作ってしまおうと思ったのが動機になります。

実現したかったこと
----

* markdownで資料を作成して`git push`するとCircleCIがスライド資料に起こしてくれる。
* スライド資料はGitHub Pagesで公開され、そのまま発表に使える。
* ついでにCircleCI上で文法上の誤りもテストしてくれる。

実現方式
----

### markdownからのスライド生成

reveal.jsが有名かと思いますが、それをRubyGem化して、コマンド一発だけでmarkdownからスライド資料への変換を可能にした[reveal-ck](https://github.com/jedcn/reveal-ck)を使います。

markdownはほぼGitHub Flavored markdownで大丈夫（絵文字もOK）ですが、`---`でページ区切りになるという点だけ覚えておけばよいかと。

### GitHub Pagesへの公開

2016年8月のアップデートで、GitHub Pagesへの公開方法が以下の3つになりました。

* `master`ブランチの`/docs`フォルダ内をPagesとして公開する。
* `master`ブランチをPagesとして公開する。
* `gh-pages`ブランチをPagesとして公開する（従来の方法）。

スライド用に生成されたファイルをフォルダに押し込める形で実現できそうなので、当初は`/docs`フォルダを使う方法を検討していましたが、スライドのGitHub Pages上での公開は発表のときだけ使えればよかったので、そういう一時的な用途で`master`ブランチを使うのは微妙だなと見直し。またCircleCIから`master`ブランチを書き換えるというのも抵抗があったので、CircleCIでは`gh-pages`ブランチを`checkout`させる形にしました。

### 文章校正

最近流行りの分野ではありますが、定番の[textlint](https://textlint.github.io/)を使いました。textlint自体初めて使ったということもあり、以下の記事を大いに参考とさせていただきました。

* [textlintをTravis CIで動かして継続的に文章をチェックする - Qiita](http://qiita.com/azu/items/e36501d25593d008f6ac)

実装
----

レポジトリ内は以下の構成になりました。

```
|-- .textlintrc
|-- circle.yml
|-- package.json
`-- (スライド用ディレクトリ)
    |-- slides.md
    `-- config.yml
```

トップにはCircleCIを設定するためのcircle.ymlと、textlintを導入するためのpackage.json、textlintの設定を書いた.textlintrcの3ファイルを置きます。package.jsonと.textlintrcの設置については先のリンク記事の内容ほぼそのまんまですが、以下の手順で生成しています。いずれもレポジトリのトップの場所で実行します。

```bash
# package.jsonを生成します。
$ npm init
# textlint等をインストールし、package.jsonに依存関係を記述。
$ npm install --save textlint textlint-rule-common-misspellings textlint-rule-preset-japanese
# node_modulesにtextlint等がインストールされますが、
# 実際はCircleCI上で逐次インストールして使うので、レポジトリからは削除していいです。
$ rm -rf node_modules
# package.jsonのscriptsにtextlint実行用のコマンドを記載しておきます。後述。
$ vim package.json
# .textlintrcは手動で生成して、後述の通り記載します。
$ vim .textlintrc
```

package.jsonは以下のように、`scripts`にtextlint実行用のコマンドを記載しておきます。これで`npm run textlint (対象ディレクトリ)`で、指定したディレクトリ内のmarkdownファイルに対してtextlintが実行されます。

```json:package.json
{
  "private": true,
  "scripts": {
    "textlint": "textlint -f pretty-error"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chroju/talks.git"
  },
  "dependencies": {
    "textlint": "^7.1.2",
    "textlint-rule-common-misspellings": "^1.0.1",
    "textlint-rule-preset-japanese": "^1.3.3"
  }
}
```

.textlintrcには適用する校正ルールを記載します。

```json:.textlintrc
{
  "rules": {
    "common-misspellings": true,
    "preset-japanese": true
  }
}
```

続いてcircle.ymlは以下の通り。

```yaml:circle.yml
general:
  branches:
    ignore:
      - gh-pages
      - master

machine:
  timezone:
    Asia/Tokyo
  ruby:
    version:
      2.3.1
  node:
    version:
      7.0.0

dependencies:
  cache_directories:
    - "/opt/circleci/.rvm/gems"
    - "/home/ubuntu/talks/node_modules"
  pre:
    - gem install reveal-ck --no-ri --no-doc
    - npm install

test:
  override:
    - npm run textlint $CIRCLE_BRANCH
  post:
    - cd /home/ubuntu/talks/$CIRCLE_BRANCH && reveal-ck generate && cp -r slides/* ../

deployment:
  github:
    branch: /.*/
    commands:
      - git config --global user.name chroju
      - git config --global user.email chor.chroju@gmail.com
      - rm .ruby-version
      - git checkout gh-pages
      - git add --all
      - git commit -m "Circle CI deploy"
      - git push git@github.com:chroju/talks gh-pages
```

まず`dependencies`でreveal-ckとtextlintをそれぞれインストールしています。textlintに関してはpackage.jsonに依存関係を書いているので、`npm install`だけで入ります。一度インストールしてしまえばその後は使いまわせるので、`cache_directories`に指定してキャッシュしておきます。

参考：[Caching RubyGems Install - Programming Languages - CircleCI Community Discussion](https://discuss.circleci.com/t/caching-rubygems-install/7467)

`test`では`npm run textlint $CIRCLE_BRANCH`でoverrideし、textlintを実行します。失敗するとこんな感じに。

![textlint failure](https://qiita-image-store.s3.amazonaws.com/0/19465/0a63dd84-584f-9d2e-8e62-ea5ed3fe8345.png)

なお、overrideに記載している通り、textlintの対象ディレクトリ名は、`$CIRCLE_BRANCH`変数でブランチ名から取ってきています。つまり新たにスライド資料を作成するときは、以下の手順になります。

1. ブランチを新しくつくる（ブランチ名は発表するイベント名や、スライドタイトルがオススメ）。
2. ブランチ名と同名のディレクトリを作成する。
3. 作成したディレクトリ内にslides.mdを置き、資料を作成する。

`test`の`post`では、このブランチ名のディレクトリに移動して`reveal-ck generate`を実行し、スライド資料を作成しています。作成されたファイル群はslidesフォルダの中に置かれますが、これをトップへ`cp`しているのは、GitHub Pagesで公開したときにトップのパスで資料が見られるようにするためです。

textlintが通れば`deployment`に移行し、`gh-pages`ブランチを生成してpushして完了です（逆に言えばtestが通らなければ、`deployment`は実行されません）。ここまで無事に終われば、GitHub Pagesでスライド資料が見られるようになっているはずです。ブラウザでアクセスして、全画面表示にすればそのまま発表に使えます。

発表が終わったら`gh-pages`ブランチは削除し、スライド用ブランチを`master`にマージしてあげれば完了です。

補足
----

### スライドの外観を変更したい

reveal.jsのスライドの外観は、slides.mdと同じ場所に置いたconfig.ymlによって設定できます。主にテーマ（色やフォント）とtransition（ページめくりのアニメーション）が変更でき、テーマの一覧は http://lab.hakim.se/reveal-js/#/themes 、transitionの一覧は http://lab.hakim.se/reveal-js/#/transitions から確認できます。また`title`を指定すると、GitHub Pagesで開いたときのページタイトルがそれになります。

```yaml:config.yml
theme: "white"
transition: "none"
author: "chroju"
title: "ポエム駆動開発"
```

### スライドをpdfで保存したい

これはreveal.jsが機能を持っています。GitHub Pagesでスライドのトップを開いたら、URL末尾に`index.html?print-pdf`を付加して開き直し、印刷画面から「PDFで保存」します。保存するとき、ページの向きは「横」にします。

参考：[reveal.js で pdf 印刷](https://gist.github.com/uupaa/7744760)

完成品
----

GitHubに上げています。

[chroju/talks: LTs](https://github.com/chroju/talks/tree/master)

`master`ブランチのものは私のLT資料を入れているので個人用ですけど、個人的なものを削除した`release`ブランチを用意しているので、そちらをフォークしてもらえればここに書いたとおりのことがすぐに実行できるはずです。詳しくはREADME.md参照で。