at kaneshin

Free space for me.

Crystal で Markdown to HTML ツールを軽く書く

f:id:laplus-knsn:20161218190221p:plain

この記事は Crystal Advent Calendar 2016 の20日目の記事です。

普段は Golang を書いている事が多いので、 Golang の人になっていますが、暇を見つけては色々な言語にも触れています。今日はその一つである Crystal Language について軽く CLI ツールを作る方法を書こうと思います。

Crystal へのモチベーション

そもそも、Crystal に触れるきっかけは「コマンドラインツール作るのに毎回 Golang で書くのはちょっとダルい… でも、 Ruby や Python のような LL 言語もできれば使いたくない…」というのがきっかけです。

Crystal はおおよそ Ruby の Syntax で記述が可能ですし、ネイティブコードを吐き出すことも可能なので、自分にとって打ってつけでした。マクロも定義できるので、慣れてしまえばかなり効率よく書けるんではないかと思い、進行形で勉強しています。

Crystal の所感

今のところ『すごくよい』の一言です。

Crystal's Goal

  • Have a syntax similar to Ruby (but compatibility with it is not a goal)
  • Statically type-checked but without having to specify the type of variables or method arguments.
  • Be able to call C code by writing bindings to it in Crystal.
  • Have compile-time evaluation and generation of code, to avoid boilerplate code.
  • Compile to efficient native code.

最高です。

Markdown to HTML

今回紹介するツールは、ある Markdown から HTML ファイルを出力するという簡単なツールとなります。データの受け取り方は下記の二つとして作成をします。

  • ファイル名をコマンドから受け取る
  • 標準入力で受け取る

ちなみに、今回作成したコードは GitHub に置いてあります。

github.com

プロジェクト作成

$ crystal init app md2html
$ cd md2html

実装

実装といっても、 Crystal が用意しているライブラリを使っているだけなので、何も難しいことはしていません。まず、プロジェクトの構成は下記のようになっています。

$ tree
.
├── bin
│   └── md2html.cr
├── LICENSE
├── README.md
├── shard.yml
├── spec
│   ├── md2html_spec.cr
│   └── spec_helper.cr
└── src
    ├── md2html
    │   └── version.cr
    └── md2html.cr

bin/md2html.cr は User Interface を提供しているアプリケーションレイヤーで、 src 配下のファイルが実際のビジネスロジックレイヤーです。(ただし、本当に標準のライブラリを使っているだけです)

src/md2html.cr

凝ったことをする場合はこのファイルをカスタマイズします。今は markdown.to_html を呼んでいるだけです。

require "./md2html/*"
require "markdown"

module Md2html
  class Md
    def initialize(@io : IO?)
    end

    def to_html(text : String?)
      io = @io
      return unless io
      return unless text
      io.puts Markdown.to_html(text)
    end
  end
end

bin/md2html.cr

ユーザがコマンドで使うときの I/F 定義です。これが実行ファイルの元になります。

require "../src/md2html"
require "option_parser"

OptionParser.parse! do |parser|
  parser.banner = "Usage: md2html [OPTIONS] FILE"
  parser.on("-v", "--version", "print version") { puts Md2html::VERSION }
  parser.on("-h", "--help", "print this help") { puts parser }
end

md = Md2html::Md.new(STDOUT)

if !STDIN.tty?
  md.to_html STDIN.gets_to_end
  exit 0
end

if ARGV.empty?
  exit 1
end

filename = ARGV.first
if !File.exists?(filename)
  puts "No such file: " + filename
  exit 1
end
md.to_html File.read(filename)

ここまでできたらビルドかそのまま実行して確認します。

# ビルド(リリース用)
$ crystal build --release ./bin/md2html

# 実行1
## ファイル名を渡す crystal run のときは -- を使用して後続をコマンドライン引数として認識させる
$ crystal run ./bin/md2html -- README.md

# 実行2
## 標準入力から渡す
$ cat README.md | crystal run ./bin/md2html

これで標準出力に HTML 形式で出力されていると思います。

リソース

公式

crystal-lang.org

https://crystal-lang.org/docscrystal-lang.org

https://crystal-lang.org/api

Playground も用意されており、最高です。

Compile & run code in

Docker Container

Docker コンテナも存在しています。環境構築が面倒な方はこちらから。

https://hub.docker.com/r/crystallang/crystal/

For Vimmer!

Vimmer 用のプラグインです。是非、導入しましょう。

github.com

おわりに

2017年はさらに Crystal を使っていこうと思います。東京で勉強会とかあれば是非誘ってもらえると飛んで行きます。