2014年4月11日金曜日

RubyのReadline.readlineでデフォルトの文字列を表示する

RubyのReadline.readlineでデフォルトの文字列を表示する方法を調べたところ、本を読む RubyのReadline.readlineで初期文字列を与えるに詳しい説明があった。コードも載っていてとても参考になるのだけど、Ruby 2.0でdeprecatedになったdlを使っているためfiddleで書いてみた。

インターフェイスは、Readline.readlineに引数を追加してデフォルトの文字列を受け取るようにした。

require "readline"
require "fiddle/import"

module Readline
  module C
    extend Fiddle::Importer
    dlload "/usr/local/opt/readline/lib/libreadline.dylib"
    
    RL_STARTUP_HOOK = import_symbol("rl_startup_hook")
    
    bind("int startup_hook_callback()") do
      preput = Readline.instance_variable_get(:@preput)
      Readline.insert_text(preput)
      0
    end
  end
  
  module ReadlineWithPreput
    def readline(prompt = "", add_hist = false, preput = nil)
      @preput = preput.to_s if preput
      
      callback = @preput ? C["startup_hook_callback"].to_i : 0
      template = %w(S! I! L! Q!).find do |template|
        [0].pack(template).size == Fiddle::SIZEOF_VOIDP
      end
      C::RL_STARTUP_HOOK[0, Fiddle::SIZEOF_VOIDP] = [callback].pack(template)
      
      super prompt, add_hist
    ensure
      @preput = nil
    end
  end
  
  class << self
    prepend ReadlineWithPreput
  end
end

dlloadの引数で与えているパスは実行環境に合わせて変更する必要がある。上のコードで指定しているのは、MacでHomebrewを使ってインストールしたGNU Readline。Macに元々ある/usr/lib/libreadline.dylibの実体はlibeditで、こちらにも対応したかったのだけど、どうやってもできなかった。fiddleの使い方が正しくないのかと思い、Cで書いてもできなかった。

使い方。

p Readline.readline("> ", false, "Default Value")