はじめに
Ruby で,メソッド名を少しだけ間違うと,「もしかしてこれ?」と言ってくれる。
たとえば円周率を表示しようとして
puts Math::PI
のつもりで
pots Math::PI
とやったら
NoMethodError (undefined method `pots' for main:Object)
Did you mean? puts
のように表示される。
意訳すれば
pots
なんつーメソッドはねーよ
もしかしてこれ?puts
となる。
存在しないメソッドを呼び出そうとしたときに,存在するメソッドの中からよく似た名前のものを探してきて,候補として表示してくれるンである。
こういう機能を自作のスクリプトにも組み込めないか?
つまり,ユーザーに何か入力させるのだが,受け付ける文字列があらかじめ限られていて,そこから外れているときに,「もしかしてこれ?」とやるわけだ。
お題
こんなプログラムを考える(実用性は考えていない)。
Ruby 関係のよく見るウェブサイトをブラウザーで開くだけ。
サイトにはあらかじめ略称を決めておき,それをコマンドライン引数として与える。
ターミナルで,たとえば
ruby open_site.rb rubima
と打てば Rubyist Magazine のページが開く。
また,
ruby open_site.rb rurema
と打てば Ruby リファレンスマニュアル のページが開く。「rurema」は Ruby リファレンスマニュアルの(たぶん公式の)英字略称だ。
ウェブサイトをブラウザーで開くには,launchy という gem を使う。
こんなコードになる:
require "launchy"
case ARGV.first
when "rubima"
Launchy.open "https://magazine.rubyist.net/"
when "rurema"
Launchy.open "https://docs.ruby-lang.org/ja/"
else
puts "Unknown"
end
ARGV
はコマンドライン引数を収めた文字列配列だ。
ARGV.first
がどれにも当てはまらなかった場合,現状では「Unknown」とだけ表示されるが,さらに「もしかしてこれ?」を表示させたい。
実装
Ruby の「Did you mean?」機能は,did_you_mean という gem で実現されている。
この gem は Ruby にバンドルされている。
そして,ありがたいことに,この記事で意図している機能を実現する手助けをしてくれるのだ。
とても簡単なので,修正後のコードをまるごと提示しよう:
require "launchy"
names = %w[rubima rurema]
checker = DidYouMean::SpellChecker.new(dictionary: names)
name = ARGV.first
case name
when "rubima"
Launchy.open "https://magazine.rubyist.net/"
when "rurema"
Launchy.open "https://docs.ruby-lang.org/ja/"
else
puts "Unknown"
candidates = checker.correct(name)
unless candidates.empty?
puts "Did you mean?"
candidates.each do |candidate|
puts " #{candidate}"
end
end
end
簡単に説明する。
まず受け付ける文字列の配列を元に DidYouMean::SpellChecker
オブジェクトを作っておく。
そして,そいつの correct
メソッドを呼び出す。すると候補の文字列の配列が返る。
近い文字列が存在しないときは空配列が返る。
ただそれだけ。