テンプレートから新規ファイルを作成するCLIツール zouch を作った

例えば新しいプロジェクトを作成するとき、何かと新しいファイルを作成する必要があります。

README.md, LICENSE, .editorconfig, あるいはC/C++ならCMakeLists.txtやmain.cppなどをプロジェクトに含める必要があるでしょうが、 そのうちいくつかは毎度似たようなファイルを「お約束」として用意することでしょう。

私の場合はそれらを既存のプロジェクトからコピーしてきたり、もしくは touch で空のファイルを作成したあとにVSCodeSublime Textといったエディタのスニペット機能を用いて中身を入力することが多いです。

しかし、touch して〜、エディタで開いて〜、というのも結構めんどくさいので touch した時点でファイルの中身も自動生成してくれればなにかと便利そうです。

そこで、zouchというCLIツールを作成しました。

github.com

これはなに

zouchを使用すると、予め登録しておいたテンプレートからファイルを作成できます。

$ cat LICENSE.md
cat: LICENSE.md: No such file or directory

$ zouch LICENSE.md

$ cat LICENSE.md
# MIT License (MIT)

Copyright (c) 2021 Ryooooooga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
...

上のようなMITライセンスのテキストファイルは、~/.config/zouch/templates に配置された以下のようなテンプレートから作成されます。

# MIT License (MIT)

Copyright (c) {{Now.Year}} {{Shell `git config user.name`}}

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
...

zouchではGo言語のテンプレートエンジンである text/template の記法を用いて出力内容をある程度動的に変更できるようになっています。 上の例で言うと {{ ... }} に囲まれている部分 (西暦N年、ユーザ名) が出力時にはいい感じに展開されているのがわかると思います。

また、touch 本来の機能であるタイムスタンプの更新もできるようになっています。1

テンプレートファイルの探索

zouchでは /path/to/foo.txt というファイルを生成する際にまず ~/.config/zouch/templates/foo.txt というファイルを探し、 これが存在していればそれをテンプレートファイルとして用います。

foo.txt~/.config/zouch/templates/ 以下に存在しないときは、代わりに _.txt という名前のファイルを探し、これを代わりにテンプレートファイルとして用います。

これによって、"特定の拡張子のファイルはすべて同じテンプレートから生成したい" という要求を解決します。 以下に例として、.goファイルのテンプレートを示します。

{{ if HasSuffix .Filename "_test.go" -}}
package {{.Filename | Abs | Dir | Base}}_test

import (
    "testing"
)

func Test{{RegexReplaceAll .Filename `_test\.go$` "" | Base | UpperCamelCase}}(t *testing.T) {
}
{{ else -}}
package {{.Filename | Abs | Dir | Base}}
{{ end -}}

_.go の例

このテンプレートは、foo/bar.go というファイルを生成しようとした場合は

package foo

というファイルを、foo/bar_test.go というファイルを生成しようとした場合は

package foo_test

import (
    "testing"
)

func TestBar(t *testing.T) {
}

というファイルをそれぞれ出力します。

インストール方法

  • go get
$ go get github.com/Ryooooooga/zouch
  • zinit (プリビルトバイナリのダウンロード)
# ~/.zshrc
zinit ice lucid wait"0" as"program" from"gh-r" \
    pick"zouch*/zouch"
zinit light 'Ryooooooga/zouch'

類似のツール

類似のツールに touch-alttouch_erb があります。

touch-alt

github.com

qiita.com

touch-alt は Node.js 製のCLIツールです。 テンプレートエンジンなどは使用せず、単にテンプレートファイルをコピーしてくるだけのシンプルなツールです。

touch_erb

github.com

touch_erb は Ruby 製のCLIツールです。 ERBを採用することで柔軟なファイルの生成が可能になっています。

zouchはこのtouch_erbから着想を得て (というか丸パクし) 作成しました。

touch_erbを用いず、わざわざ自作したのは以下のような理由のためです。

  • dotfilesRubyのインストールを前提としていない
  • 宗教的な問題でグローバルに gem install したくない2

zouch の作成にあたって

zouchの実装言語にはGoを選択しました。 以下のような言語も選択肢には入っていましたが、シングルバイナリにコンパイルできること、 テンプレートエンジンの言語標準として text/template ライブラリが存在していることが選定の決め手となりました。3

  • Shellスクリプト: 処理系のインストールが不要 / 書くのが辛い
  • Perl: 処理系のインストールが不要 / わからない
  • Node.js: dotfiles内でインストールしている / npm i -gしたくない
  • Python: dotfiles内でインストールしている / pip install したくない, 書きたくない
  • C++ (+ Luaなどの組み込みスクリプト): シングルバイナリにコンパイルできる / 実装コストが重い
  • Rust: シングルバイナリにコンパイルできる / テンプレートエンジンとして標準的なものが存在しない

一方、テンプレート内で任意のRubyスクリプトを書けるERBとは違い、 Goのtext/templateの表現力は極めて限定的です。 例えば、テンプレート内で使用できる関数は (素朴な実装に於いては) コンパイル時に登録されたものに限られます。

そこで、Shell関数を通じて任意のシェルスクリプトを実行できるようにすることで表現力の低さを補うことにしました。


テストを書きやすいようにパッケージの分割を工夫したりはしたものの、この手のCLIツールはテストを書くのがすごく面倒です。 あとドキュメントを書くのも面倒。

それからGoの time.Time.Format() の仕様を考えた人は早く謝罪の言葉を考えたほうがいい。


  1. touchに付いているオプションなどは実装していないため完全な上位互換として用いることはできませんが、大雑把な方なら alias touch=zouch してもある程度大丈夫だと思います。多分。

  2. pip installもしたくない。npm install -g もできればしたくない。

  3. 個人的にGo言語は好きでない。 (C言語を書いている気持ちになるため)