Rubocopに怒られないように0埋めした桁揃え文字列を作る

TL;DR

1〜3桁の整数sizeがあり、そこから例えばL064.datみたいなファイル名を作りたい時、Rubocopに怒られないようにするには

filename = format("L%<size>03d.dat", size: size)

とすれば良い。これだけなんだけど、ここまで来るのに妙に苦労したのでRubocopの文句からこの記事にたどり着けるように詳細を書いておきます。

Rubocopに怒られる

C言語っぽく

1〜3桁の整数sizeがあり、そこから例えばL064.datみたいなファイル名を作りたい。C言語から入った人ならsprintf使ってこんな感じに書くと思う。

size = 10
puts sprintf("L%03d.dat", size)

これはRubocopに以下のように怒られる。

$ rubocop format1.rb
Inspecting 1 file
C

Offenses:

format1.rb:2:6: C: Style/FormatString: Favor format over sprintf.
puts sprintf("L%03d.dat", size)
     ^^^^^^^
format1.rb:2:16: C: Style/FormatStringToken: Prefer annotated tokens (like %<foo>s) over unannotated tokens (like %s).
puts sprintf("L%03d.dat", size)
               ^^^^

1 file inspected, 2 offenses detected

とりあえずsprintfを使うな、formatを使え、と言ってるようですね。Rubyには書式付き文字列の作成方法が複数あるため、フォーマットを統一するため、RubocopのStyle/FormatStringでどれを使うか指定できますが、デフォルトではEnforcedStyleformatとなっています。

formatを使う

というわけでformatを使いましょう。sprintfの代わりにformatって書いただけ。

size = 10
puts format("L%03d.dat", size)

これはまたRubocopに怒られる。

$ rubocop format2.rb
Inspecting 1 file
C

Offenses:

format2.rb:2:15: C: Style/FormatStringToken: Prefer annotated tokens (like %<foo>s) over unannotated tokens (like %s).
puts format("L%03d.dat", size)
              ^^^^

1 file inspected, 1 offense detected

Unannotated tokensを使うなと言ってるようですね。

Template tokensを使う

さて、ここで渡しは先の文句ではannotated tokensを使え、と書いてありますが、それに気づかずに(annotated tokenとtemplate tokenの違いを理解せずに)template tokenを使いました。

Template tokensというのは、formatの文字列に引数としてハッシュを渡すと、%{name}などで、キーを参照してそのまま代入してくれる機能です。ただし、%{name}を使うとフォーマットできないので、フォーマットしたい場合はフォーマット済み文字列を渡す必要があります。こんな感じでしょうか。

size = 10
puts format("L%{size}.dat", size: size.to_s.rjust(3, "0"))

なんか不必要に複雑になった上に、まだRubocopに怒られますね。

$ rubocop format3.rb
Inspecting 1 file
C

Offenses:

format3.rb:2:15: C: Style/FormatStringToken: Prefer annotated tokens (like %<foo>s) over template tokens (like %{foo}).
puts format("L%{size}.dat", size: size.to_s.rjust(3, "0"))
              ^^^^^^^

1 file inspected, 1 offense detected

Template tokens %{foo}ではなく、annotated tokens%<foo>sを使え、と言ってますね。ここで初めてannotated tokenというものがあることを知りました。

Annotated tokensを使う

Annotated tokensは、templated tokensと同様に引数にハッシュを与えますが、%<name>dのようにフォーマット指定できます。これを使うとこうなりますかね。

size = 10
puts format("L%<size>d.dat", size: size)

上記はRubocopは文句を言いませんが、出力結果が0埋めされません。

Annotated tokensを使って、かつ桁指定して0埋めする

Annotated tokensでは、printfと同様にフォーマット指定子が使えます。例えば、3桁の整数で左を0埋めしたければ、%<name>03dと書けます。

size = 10
puts format("L%<size>03d.dat", size: size)

ようやくRubocopに文句を言われずに、0埋めした桁揃え文字列を作ることができました。

まとめというか・・・

普段VSCodeを使ってますが、ちょっとしたスクリプトはVimで書いてます。で、VimでRubocopで確認するようにしてみたらRubocopのうるさいことうるさいこと。自動的に直せるところは直すauto-correctもあるのですが、保存時にこれが走るようにしたらかえって鬱陶しかったので、今のところ手でなおしています。

「自由に、好きなように書ける」のがRubyの良いところだと思っているのですが、その思想とLinterはどうも相性が悪いような気がしますね・・・

参考

  • [RuboCop Style/FormatString](https://qiita.com/tbpgr/items/d11b28753dc893920db6)