読者です 読者をやめる 読者になる 読者になる

woshidan's blog

そんなことよりコードにダイブ。

irbの実行結果の出力が非常に長い場合、途中からカットしたい

最近しばしば行う作業のログが、おそらく人間の目には確認できないほど長く、また件数的におそらくログから確認を行うことはしない*1ということもあり、じゃあ使わないログはある程度短くしたいですね、ということで調べました。

具体的には、属性がたくさんあるクラスについてのログで、最初の方のフィールドしか確認する必要がない場合、後半は出さなくてもいいのでは...というケースを対象としています。

# この例だとuser_idあたりまでがあれば業務上十分
irb(main):045:0> Memo.new
=> #<Memo:0x007fcf43045500 @id=123456, @user_id=12, @category_id=3456, @sub_category_id=nil, @reminded_at=2016-10-22 10:00:00 +0900, @created_at=2016-10-21 10:00:00 +0900, @updated_at=2016-10-21 10:00:00 +0900>

動作確認用の雑なクラス定義はこちらのgistにあります。

irbの実行結果の出力形式を変更する

IRB::Inspector.def_inspectorメソッドで、irbの実行結果の出力形式を独自に設定することができます。具体的には、引数に設定の名前としてのkeyとブロックを与えることで、ブロックの返り値を出力として利用できます。

なお、このメソッドは .irbrcというファイルに記述します。

# ~/.irbrc
# たとえば出力を2回表示するように設定してみます
IRB::Inspector.def_inspector([:test]){|v| v.to_s * 2 }

.irbrcに書いた上記の設定を適用するには、起動時に--inspectオプションで指定するかirb起動後にIRB.conf[:INSPECT_MODE]変数から設定します。

$ irb --inspect test
irb(main):001:0> abc = "def"
=> defdef

この要領で、出力結果の表示を50文字までにするような設定を書いてみます。

IRB::Inspector.def_inspector([:test2]){|v| v.to_s.slice(0..50) }
$ irb --inspect test2
# (クラス定義中...)
irb(main):015:0> Memo.new
=> id: 123456 user_id: 12 category_id: 3456 sub_catego

これでいいかなと一瞬思ったのですが、この方法の場合、出力に使う返り値のクラスが適切にto_sメソッドを実装している前提なので、いちいち各クラスのto_sメソッドを確認したり定義したりする必要があって大変そうです。

また、ppメソッドなどの出力結果を利用することも考えたのですが、ppメソッドの戻り値は、出力対象のクラスなので思っていたより簡単にはいきません。

irb(main):015:0> require 'pp'
=> true
irb(main):017:0> res = Memo.new
=> #<Memo:0x007fefe5068ad8 @id=123456, @user_id=12, @category_id=3456, @sub_category_id=nil, @reminded_at=2016-10-22 10:00:00 +0900, @created_at=2016-10-21 10:00:00 +0900, @updated_at=2016-10-21 10:00:00 +0900>
irb(main):018:0> res
=> #<Memo:0x007fefe5068ad8 @id=123456, @user_id=12, @category_id=3456, @sub_category_id=nil, @reminded_at=2016-10-22 10:00:00 +0900, @created_at=2016-10-21 10:00:00 +0900, @updated_at=2016-10-21 10:00:00 +0900>
irb(main):019:0> res.class
=> Memo

また、IRB::Inspector.def_inspectorメソッドで紹介したirbの出力方式は当然独自方式だけではなく、yaml, ppなどいくつかのオプションがあり、そちらを使っている場合も多い中、出力方式そのものを変えることができない場合もあるでしょう。

では、最終的に出力が渡ってきた後で、その文字列を切り取る方法がないかな、と思って、irbのプロンプトそのもののカスタマイズについて調べました。

irbプロンプトのカスタマイズ

irbプロンプトの設定をする際も、.irbrcに記述します。

記述項目は、IRB.conf[:PROMPT]で、下記のような形式となっています。

IRB.conf[:PROMPT][:SAMPLE] = {
  :PROMPT_I => "%N(%m):%03n:%i> ",  # 通常時のプロンプト
  :PROMPT_N => "%N(%m):%03n:%i> ",  # 継続行のプロンプト
  :PROMPT_S => "%N(%m):%03n:%i%l ", # 文字列などの継続行のプロンプト
  :PROMPT_C => "%N(%m):%03n:%i* ",  # 式が継続している時のプロンプト
  :RETURN => "==> s\n"   # メソッドから戻る時のプロンプト
}

%sとあって、あ、これCのprintfっぽいな、と思って http://d.hatena.ne.jp/iww/20090701/printf などを参考に、英数字40文字あたりで切れてください、というつもりで書いた設定は下記となります。

IRB.conf[:PROMPT][:MY_PROMPT] = {
  :PROMPT_I => "%N(%m):%03n:%i> ",  # 通常時のプロンプト
  :PROMPT_N => "%N(%m):%03n:%i> ",  # 継続行のプロンプト
  :PROMPT_S => "%N(%m):%03n:%i%l ", # 文字列などの継続行のプロンプト
  :PROMPT_C => "%N(%m):%03n:%i* ",  # 式が継続している時のプロンプト
  :RETURN => "==> %.80s\n"   # メソッドから戻る時のプロンプト
}

上記の設定を適用するには起動時に--promptオプションで指定します。

irb --prompt my-prompt
# (クラス定義中...)
irb(main):015:0> Memo.new
==> #<Memo:0x007f99b2010920 @id=123456, @user_id=12, @category_id=3456, @sub_categor

ためしに、出力形式をyml形式に変えても途中で出力をカットするように動いているか見てみます。

irb(main):017:0> conf.inspect_mode = :yaml
==> --- :yaml
...

irb(main):018:0> Memo.new
==> --- !ruby/object:Memo
id: 123456
user_id: 12
category_id: 3456
sub_category_id:

よさそうですね。

仕事で導入するのはやや怖いところもありますが、興味が出たので調べてみた結果はこんな感じです。

現場からは以上です。

参考

*1:ログは長いけれど、作業時間は短いのでやり直したほうが正確っぽい