MItamaeでdotfilesを管理してみた
MItamaeを使ってdotfilesを管理してみたら、お手軽に良い感じになったので紹介します。
そもそもMItamaeって?
プロビジョニングツール(Infrastructure as Code)であるitamaeを、mrubyで実装したものになります。
itamaeの文法が使えるため、シェルスクリプトやMakefileと違った、Ruby DSLライクな構成管理が可能になります。 そのため、RubyやChefを触ったことがある人は特に導入がしやすいかと思います(itamaeはChefの簡易版)。
itamaeじゃダメなの?
itamaeでは、実行時にRubyの環境が必要です。MacのようにデフォルトでRubyがインストールされていれば問題ありませんが、そうではない場合、Rubyの環境を自前で用意する必要があります。
MItamaeは環境に左右されないの?
mruby実装を生かして、コンパイルされた実行用のシングルバイナリが配布されています。カーネルによって対象のバイナリを変える必要があるため、そこだけはローカル環境に合ったものを、 wget
や curl
で落とす必要があります。
MItamaeを用いたdotfiles
私のdotfilesを参考にしながら、ディレクトリ構成と処理の流れを整理していきます。
ディレクトリの構成
以下のようになりました。
➜ tree -aL 2 . ├── .gitignore ├── LICENSE ├── README.md ├── bin │ ├── mitamae -> mitamae-1.9.5 │ ├── mitamae-1.9.5 │ └── setup ├── cookbooks │ ├── fish │ ├── functions │ ├── git │ ├── homebrew │ ├── nvim │ ├── python2 │ ├── python3 │ ├── rbenv │ ├── tfenv │ └── tig ├── install.sh ├── lib │ ├── recipe.rb │ └── recipe_helper.rb └── roles ├── base └── darwin
上から順に
bin/
... シングルバイナリの管理ディレクトリ。cookbooks/
... パッケージごとの設定ディレクトリ。install.sh
... dotfilesの起点スクリプト。lib/
... MItamaeの起点ディレクトリ。roles/
... カーネルごとの設定ディレクトリ。
処理の流れ
install.sh
を実行する。bin/setup
がキックされる。install.sh
に戻り、bin/mitamae local lib/recipe.rb
を実行する。bin/mitamae
がitamae
コマンドの起点になる。
- ローカルのカーネルと同じ
roles
設定ファイルを実行する。例えばMacなら、roles/darwin/
の設定を読み込むといったもの。 - パッケージごとの設定を記した、
cookbooks
を実行する。
dotfilesをMItamaeにして感じたメリット
Ruby DSLで書ける
経験不足もありますが、シェルスクリプトやMakefileのみのコードを保守し続けるのは少し敷居が高いです。その点MItamaeは、慣れているRuby DSLを使って保守し続けることができます。導入しようと思ったきっかけのひとつです。
itamaeのレールに乗れる
dotfilesの管理にプロビジョニングツールを使えるのは、メリットのひとつだと思います。例えば、itamaeの文法そのままにディレクトリを掘ったり、シンボリックリンクを貼る処理が書けます。公式が提供している構成のベストプラクティスも利用できます。
MItamaeのdotfiles実践プラクティス
ここでは開発中に、「こうすると良いのでは」と感じたことを整理したいと思います。
開発はitamaeで
開発環境では、itamaeコマンドを使えるようにしておくと良いです。例えばディレクトリを掘るために、 mkdir cookbooks/fish
を打つといった手作業がなくなります。主に使うコマンドは
- itamae generate [cookbook | role]
- itamae destroy [cookbook | role]
のふたつだと思います。 詳しくは itamae help
で確認しましょう。
シンボリックリンク処理を再定義しない
シンボリックリンクを貼る処理を、 dotfile
や ln
といった名前で再定義しているケースがあります。私も最初は以下のように処理をまとめていました。
define :ln do name = params[:name] link node[:xdg_config_home] do to File.expand_path("../../#{name}/files/#{name}", __FILE__) user node[:user] force true end end
しかし、これだと以下のような問題が発生します。
結局ブロック内で再定義する状況が生まれるなら、 わざわざ ln
関数を作る必要はないと思います。また ln
内を変更するとき、処理が破壊されていないか気を揉む必要があります。
こうなると例外として、 link
リソースを使いたくなるケースもあるかもしれません。ですが、シンボリックリンクを貼る処理が複数あるのは混乱の元です。どちらかにまとめたほうが保守はしやすいでしょう。
おわりに
MItamaeを使ったdotfiles管理は初めてだったのですが、今のところ不満はありません。
俗に言う「学習コスト」も
が主になるので、取り立てて難しくないでしょう。
時期も時期ですので、新しいPC移行用のdotfilesをMItamaeで作ってみてはどうでしょうか。
Rubyで文字コードを扱ってて四苦八苦したのでまとめてみた
TL;DR
文字コードってUTF-8で統一すれば良いんでしょ?くらいの認識しか持っていなかったWebエンジニアマンが、実際にちゃんと文字コードについて学びまとめてみた話
この記事ってどんな人が対象なの?
B2B系の仕事に関わる人は得になるかもしれません(の割にはJavaではなくRubyの話ですが)。
理由としては、仕事をしているとクライアントの使っているOSの状況などで、様々な文字コードに対応することがあるかと思います。私の前職はC2Cで、ユーザが直接打ち込んでくるものに対応することが多かったので、SQLインジェクションやそれに対するエスケープ処理などを気にかけていました。しかし、文字コードに関してなにかしら注意を払うといったことはありませんでした(更に言えば、私は社内の管理画面なども手がけていたので、より気にする必要がなかった)。
そもそも文字コードって
UTF-8
Shift_JIS
ASCⅡ
EUC
みたいなやつらです。
文字はどうやって表現されるのか
バイト列 + エンコーディング情報 = 文字表現
すなわち、あるバイト列は文字コードに関する情報を持つことで初めて文字として表示されるということ。バイト列が文字コードと対応していれば、それにあった文字が表示されます。
Rubyの文字列の扱いをバイト列から探ってみる
Rubyには String#bメソッドとString#bytesメソッドがある。それぞれの役目についてまとめると
String#b ... ASCII-8BITの文字列の複製を返す
String#bytes ... 整数値(10進数)で文字列中のバイトを繰り返し取り出す
このふたつのメソッドを使って実際に文字列を解体してみる。
>> a = 'あ' => "あ" >> a.encoding => #<Encoding:UTF-8> >> a.b => "\xE3\x81\x82" >> a.b.encoding => #<Encoding:ASCII-8BIT>
この \xE3\x81\x82
は文字エンコーディングASCII-8BIになる。
ところで、ASCII-8BIとはなんだろう。ASCⅡのお仲間なのかな?と思って調べたところこんな記事がヒットした。引用すると
ASCII-8BITは基本的に現実のエンコーディングではなく、任意のバイトストリーム(0から > 255までの値をとるバイト)を表すものであり、生のバイトストリームに用いたり、文字列のエンコーディングが不明であることを明示したりするときに用いられます。
とのこと。上記は日本語に対してString#bメソッドを使っているので、ASCⅡの範囲外である日本語レシーバはバイト列がそのまんま返ってくるよということである。逆に言えば名前が示す通り、ASCⅡの範囲(1文字を7bitで表している)であれば文字を表示することができる。
>> 'hoge'.b => "hoge"
ちなみに String#b
は16進数で返ってくる。これは人間でも理解しやすいようにしているためである。
>> a.bytes { |b| puts b.to_s(16) } e3 81 82 => "あ"
ここで文字コードの対応表を見てみる。
あ
の部分を確認すると E38182
となっている。つまり上のバイト列はUTF-8に対応したバイト列であることが分かる(UTF-8のエンコーディング情報をもって初めて意味をなすということ)。この対応したという部分が符号化文字集合ということである。
>> a.b.force_encoding('UTF-8') => "あ"
ちなみにString#encodeで UTF-8
を指定しても上手くいかない。
String#encodeは現在のエンコーディングが正しいと仮定した上で動作するものだからである。
>> a = 'あ' => "あ" >> a = a.encode('Windows-31J') => "\x{82A0}" >> a.encode('UTF-8') => "あ"
一方ASCII-8BITは、
文字列のエンコーディングが不明であることを明示したりするときに用いられます
ということなので、そもそものエンコーディングが不明状態のバイト列になります。
しかし、UTF-8の並びをしているので、String#force_encodingを用いることで あ
を表示することができます。もし結合をしたい場合はString#force_encodingを用いて対処しましょう。
>> a = 'あ' => "あ" >> b = 'あ'.b => "\xE3\x81\x82" >> a + b Traceback (most recent call last): 2: from /Users/asadashougo/.rbenv/versions/2.5.0/bin/irb:11:in `<main>' 1: from (irb):3 Encoding::CompatibilityError (incompatible character encodings: UTF-8 and ASCII-8BIT) >> a + b.force_encoding('UTF-8') => "ああ"
最初から不正なバイト列はどうなるの?
さきほどの話はそもそもバイト列として正しいものになります。
なぜならちゃんと日本語の あ
からバイト列を作ったからです。
>> 'あ'.bytesize => 3 >> 'あ'.b.bytesize => 3
どちらも3バイトになっています(ちなみにUTF-8は3バイトで日本語を表記しています)。しかし不正なバイト列ならどうでしょう。
>> a = "\x82\xa0" => "\x82\xA0" >> a.encoding => #<Encoding:UTF-8> >> a.bytesize => 2 >> a.valid_encoding? => false
UTF-8のエンコーディングなのに2バイトなので不正なバイト列だと言うことが分かります。
これに対処するにはString#scrubを利用すると不正なバイト列を別の文字に代替することができます。
>> a.scrub => "��" >> a.scrub.valid_encoding? => true