5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Crystal言語の紹介と、Crystalで日報ファイル作成スクリプトを(再)実装した話

Last updated at Posted at 2022-03-06

はじめに

最近個人的に注目しているプログラミング言語にCrystalがあります。
そのCrystalが今年になって1ようやくWindows (x86-64)向けのコンパイラが(まだunsupported扱いですが)公式からリリースされるようになりました。

筆者は普段の業務でWindowsを使用しており、業務で度々使用する各種便利スクリプトをPythonで書いていました。
なので、これを機にそれらのPythonスクリプトをCrystalで書き直してみたくなりました。

そこでまず手始めに、毎日使用している日報ファイル作成スクリプトをCrystalで(再)実装してみることにしました。

Crystalとは

Crystalの特徴という名の個人的な注目ポイントです。

  • Rubyの強い影響を受けた、読みやすく書きやすい構文を持つスクリプト言語
  • ただしRubyと違い、静的型付き言語
  • 優秀な型推論があるため、あまり型を意識することなくコーディングが可能
  • 型安全・null安全2
  • バックエンドにLLVMを用いており、高速なネイティブコードにコンパイル可能
  • シングルバイナリを生成できるため、配布や実行環境の構築がとても簡単
  • 標準ライブラリが充実しており、かゆいところに手が届く

今回実装したいスクリプトの動作

  1. テンプレートファイルtemplate.mdを読み込む
  2. テンプレートファイルの中の特定の文字列を今日の日付や曜日に変換する
  3. それを指定のパス./年/月/daily_年月日.md(e.g. ./2022/03/daily_20220304.md)に保存する
  4. 生成された.mdファイルを、関連付けられたテキストエディタで開く

実際の実装

環境

実行環境はWindows 10 Pro (64 bit)、
使用したCrystalのバージョンは

Crystal 1.3.2 [932f193ae] (2022-01-18)

LLVM: 10.0.0
Default target: x86_64-pc-windows-msvc

です。

スクリプト本体

open_daily_report.cr
require "file_utils"
require "json"

def get_day_of_week_name(time : Time, config : JSON::Any) : String
  case time.day_of_week
  in Time::DayOfWeek::Monday    then config["dayOfWeek"]["monday"].to_s
  in Time::DayOfWeek::Tuesday   then config["dayOfWeek"]["tuesday"].to_s
  in Time::DayOfWeek::Wednesday then config["dayOfWeek"]["wednesday"].to_s
  in Time::DayOfWeek::Thursday  then config["dayOfWeek"]["thursday"].to_s
  in Time::DayOfWeek::Friday    then config["dayOfWeek"]["friday"].to_s
  in Time::DayOfWeek::Saturday  then config["dayOfWeek"]["saturday"].to_s
  in Time::DayOfWeek::Sunday    then config["dayOfWeek"]["sunday"].to_s
  end
end

def format(str : String, time : Time, config : JSON::Any) : String
  time.to_s(str.gsub("%-a", get_day_of_week_name(time, config)))
end

config = File.open("config.json") do |f|
  JSON.parse(f)
end

current_time = Time.local
template_file = Path[config["templateFile"].to_s]

dir_name = Path[format(config["outputDir"].to_s, current_time, config)]
file_name = Path[dir_name, format(config["outputFile"].to_s, current_time, config)]

unless File.exists?(file_name)
  FileUtils.mkdir_p(dir_name)
  File.open(file_name, "w") do |f|
    f.puts format(File.read(template_file), current_time, config)
  end
end
Process.new("cmd.exe", ["/c", "start", file_name.expand.to_s])
exit

設定ファイル

config.json
{
    "templateFile": "template.md",
    "outputDir": "./%Y/%m",
    "outputFile": "daily_%Y%m%d.md",
    "dayOfWeek": {
        "monday": "月",
        "tuesday": "火",
        "wednesday": "水",
        "thursday": "木",
        "friday": "金",
        "saturday": "土",
        "sunday": "日"
    }
}

テンプレートファイル

template.md
# %Y年%m月%d日(%-a)日報

## 今日やったこと

- 

## 明日やること

- 

## 連絡事項とか

- 

使い方

上記の3ファイルを同一のディレクトリに保存してやり、PowerShell等のお好きなシェルで

crystal run open_daily_report.cr

として実行してあげる3と、設定されたパスに下記のような日報ファイルが生成され、.mdが関連付けられたテキストエディタで開かれます。

./2022/03/daily_20220304.md
# 2022年03月04日(金)日報
(略)

後は開かれたテキストエディタ上で日報を書いて、決められた提出先に提出するだけです。

使用できるフォーマット/ディレクティブについて

設定ファイルやテンプレートには、%Y%m%dなどTime::Formatで定められたディレクティブ(指示文字列)を使用できます。
CrystalのディレクティブはCのそれPythonのそれとほぼ互換性があるようですが、一部独自拡張があるようです。

また、Crystalでは現状で曜日等にロケールを使用できない(i.e. 日月火……みたいな日本語の曜日名を使用できない)ようだったので、%-aという独自拡張を作り、それを設定ファイルで設定した曜日名に置換するようにしました。

既知の不具合

現時点(Crystal 1.3.2)において、Windows環境ではTime.localが常にUTCでの時刻を返すようです。
そのため、本スクリプトもUTCでの日時が適用されます。
例えばタイムゾーンが日本に設定されたWindows環境で上記のテンプレートを使用した場合、08:59以前はディレクティブが前日の日付に置換されます4

シングルバイナリにビルドする

想定どおり動作することが確かめられたので、実行ファイルを生成してみましょう。

お好みのシェルで

crystal build open_daily_report.cr --release

としてやると、同じディレクトリにopen_dialy_report.exeが生成されます。
次回からはこのexeを実行するだけで、日報ファイルが生成されるようになります。

また、他のPC等に配布する際も、このexeとテンプレート、設定ファイルの3つを配るだけでOKになりました。

自分が書いたスクリプトを他人に使ってもらうのに、いちいち「あらかじめ○○っていう言語の処理系をインストールしてパスを通しておいてください」みたいな事前準備が要らなくなるのは嬉しいですね。

おわりに

これだけ短いスクリプトを書いただけでも、Crystalは

  • 簡単に習得できる
  • 型安全・null安全なコードをサクッと書ける
  • 強力な標準ライブラリによって目的を簡単に実現できる
  • 高速なネイティブコードを出力・実行可能

と、とても便利なLightweight5 Languageだということを実感できました。

一方で、現状では

  • 特にWindows環境ではまだ不具合も多い
  • まだまだ公式のリファレンス以外に情報が少ない(日本語・英語とも)
  • 公式のリファレンスも情報が少なかったりする(特に標準ライブラリ周り)ので、結局ソースコードから実装を読み解く羽目になることもある

といった課題も感じました。

Crystalの今後のさらなる発展と普及に期待しつつ、他にも色々とスクリプトを書いてみようと思いました。

参考文献

  1. 2022/01/06リリースの1.3.0から

  2. null安全はモダンな言語なら当然かもしれませんが

  3. コンパイラへのPATHは既に通してあるものとします。

  4. 筆者は業務が終了する夕方~夜のタイミングで日報を書き始めることが多いので、この不具合も特に困ることはありませんでした。ただ、9時より前に日報を書き始める方(今日やった業務と所要時間をリアルタイムで記録したい方など)は困るかもしれません。

  5. 「お手軽」という意味でも「軽量」という意味でもLightweightですね。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?