- tl;dr
- 作ったもの
— リポジトリ
— 簡単な使い方
—– Install
—– to_json
—– holiday?()
—– when?()
—– all() - メモ
— パイプライン演算子使いまくり
— CSV データをどこに配置するか
— ドキュメント
— テスト
— Travis CI を利用したノーメンテナンス的継続的デリバリー - 以上
tl;dr
最近, 祝日・休日について悩むことがあったので, 内閣府が提供する祝日・休日 csv データを Elixir から利用するライブラリを作ってみました. そして, 年が変わっても継続的にライブラリが運用出来るようにしてみたのでメモしたいと思います.
作ったもの
リポジトリ
github.com
hex.pm
簡単な使い方
Install
- mix.exs で以下のように書いておきます
defp deps do [ {:holidays_ex, "~> 0.0.4"}, ] end
mix deps.get
でインストールしちゃいます
mix deps.get
to_json
JSON フォーマットで欲しい場合には, 以下のように to_json/0
を利用します.
iex> HolidaysEx.to_json "{\"2019-11-03\":\"文化の日\",\"2018-09-23\":\"秋分の日\"..."
holiday?()
日付から祝日・休日名が欲しい場合には, 以下のように holiday?/1
を利用します.
iex> HolidaysEx.holiday?("2019-11-03") "文化の日"
when?()
祝日・休日名から日付が欲しい場合には, 以下のように when?/1
を利用します.
iex> HolidaysEx.when?("海の日") ["2017-07-17", "2018-07-16", "2019-07-15"]
all()
単純に Map で欲しい場合には, 以下のように all/0
を利用します.
iex> HolidaysEx.all [ {"2017-01-01", "元日"}, {"2017-01-02", "休日"}, {"2017-01-09", "成人の日"} ... ]
どんなところで利用するかは貴方次第…きっと, 誰かの役に立つかもしれません.
メモ
パイプライン演算子使いまくり
正しい実装なのか解らないけど, File.stream!
で読み込んだ CSV データ (事前に UTF-8 に変換している) をパイプライン演算子を駆使して Map に変更, さらにその Map をパイプライン演算子でいい感じで加工しています.
... def all() do generate_map_data() |> Enum.sort end defp generate_map_data() do CSV.decode!(File.stream!(Path.join(:code.priv_dir(:holidays_ex), "data.csv")), headers: false) |> Stream.drop(1) |> Stream.map( fn( [ date, name ] ) -> %{ date => name } end ) |> Enum.flat_map( fn m -> Map.new(m) end ) |> Map.new end end ...
少ないコードで思ったような出力を得られることに感動しました…
CSV データをどこに配置するか
ライブラリが呼ばれる度に CSV をダウンロードしていては, 内閣府に怒られてしまうので, ライブラリ内に csv ファイルを保持する実装にしました. 以下のようなルールでファイルを置いておけば, ライブラリを利用するアプリケーションからもデータを参照出来ました.
priv
というディレクトリを作成して, そのディレクトリ以下に csv ファイルを設置する- 以下のように
:code.priv_dir(:holidays_ex), "data.csv
” と書くことでdata.csv
を参照可能
Path.join(:code.priv_dir(:holidays_ex), "data.csv")
詳細は要確認ですが, :code.priv_dir()
という書き方は Erlang で利用されている書き方のようです.
ドキュメント
以下のようにコード内にアノテーションを書いておくと, イイ感じでドキュメントも生成してくれます.
... @doc """ Specifying a date returns a holiday's name. ## Examples iex> HolidaysEx.holiday?("2019-11-03") "文化の日" """ def holiday?(date) do generate_map_data() |> Map.fetch!(date) end ...
以下のようにドキュメントが生成されます.
テスト
ライブラリを配布するからには, テストも書きたいので, 簡単に以下のようなテストを書きました.
defmodule HolidaysExTest do use ExUnit.Case doctest HolidaysEx, except: [:moduledoc, all: 0, to_json: 0] setup_all do {:ok, days: ["2017-07-17", "2018-07-16", "2019-07-15"], holiday: "元日"} end test "Get Umino-Hi's dates", state do assert HolidaysEx.when?("海の日") == state[:days] end test "Get holiday's name by date", state do assert HolidaysEx.holiday?("2018-01-01") == state[:holiday] end end
doctest
を付与していると, ドキュメントに記載している Example のコードが正しく動くかのテストもしてくれます. また, excpet
にテストを除外する関数名を記載します. 尚, Elixir School のテスト によると…
Elixir でのモックに対する単純な解答は、使うな、です。本能のままにモックへと手を伸ばしているかもしれませんが、 Elixir のコミュニティや正当な理由からはとても推奨されていないものです。
とのことなので, setup_all
マクロ内に各関数の期待する戻り値を書いておくことで対応しました.
やっぱり, モックを使うことにしたので, 別記事で簡単にモックライブラリの使い方をまとめる予定です.
Travis CI を利用したノーメンテナンス的継続的デリバリー
内閣府がデータを提供し続けている限り, ノーメンテナンスでこのライブラリを提供したいと考えました. ということで, Travis CI の cron を利用してライブラリをテストして, mix hex.publish
するように .travis.yml を以下のように
language: elixir sudo: false elixir: - '1.6.4' otp_release: - '20.0' cache: directories: - _build - deps before_install: - mkdir -p ~/.hex/ - openssl aes-256-cbc -K $encrypted_xxxxxxxxx1_key -iv $encrypted_xxxxxxxxx1_iv -in hex.config.enc -out ~/.hex/hex.config -d script: - wget http://www8.cao.go.jp/chosei/shukujitsu/syukujitsu_kyujitsu.csv - iconv -f Shift_JIS -t UTF8 syukujitsu_kyujitsu.csv > priv/data.csv - MIX_ENV=test mix test deploy: provider: script script: >- mix deps.get && wget http://www8.cao.go.jp/chosei/shukujitsu/syukujitsu_kyujitsu.csv && iconv -f Shift_JIS -t UTF8 syukujitsu_kyujitsu.csv > priv/data.csv && echo -n $HEX_LOCAL_PASSWORD | mix hex.publish --no-confirm && mix clean && mix deps.clean --all
こちらの .travis.yml をかなり参考にさせて頂きました. 有難うざいました.
事前に, hex.config を travis コマンドで暗号化した上で, hex local password を環境変数に登録しておく必要があります.
travis encrypt-file hex.config
travis コマンドを利用して Travis CI にログインした状態で travis encrypt-file
を実行すると, hex.config.enc を生成した上で, 以下のようにデコードする為のコマンドも出力されます.
openssl aes-256-cbc -K $encrypted_xxxxxxxxx1_key -iv $encrypted_xxxxxxxxx1_iv -in hex.config.enc -out ~/.hex/hex.config -d
ちゃんとテストと mix hex.publish
が実行されることを確認したら, Travis CI の cron を設定しました.
一つに一回くらい動かしておけば良いでしょう. もし, 国の気まぐれで csv データの変更 (祝日・休日が変わった場合) が入った場合, 気付いたら手動でビルドを実行すればいいかなーくらい感覚です.
ちゃんと動いてくれると嬉しいなあ.
以上
Elixir で初めてライブラリを作ってみましたが, @piacere_ex さんが Twitter で以下のように言及されているように, パイプライン演算子を駆使することで, 思ったよりも簡単にやりたいことが実現出来たので, 高い生産性を実感することが出来ました (実際に作ろうと思って, Hex にリリースするまでの所要時間は 6 時間くらい).
ヌーラボと言えば、Backlog API/Zendesk API/Redmine APIを使って、業務で使う各種チケットシステム差異を気にせず、一元管理UIを、Elixir+Phoenixで作ったりもしてる? #fukuokaex
1時間位で作ったが、ElixirのJSON API捌きと、関数型言語でのWeb開発が、既存言語ともはや別世界な生産性だから? pic.twitter.com/upPTTr1Ml6
— piacere (@piacere_ex) 2018年4月13日
また, エコシステムについても, Ruby の gem や Rake に似たツールが利用出来る為, ある程度は雰囲気で扱うことが出来ました. 言語仕様等については, まだまだ勉強付属ですが, 今後も Elixir の良さを活かせるようなライブラリを書けるようになりたいと思います.
オッツデース.