見出し画像

DOS画面での入力処理は面倒臭いですよね。

GUI環境では、リストボックス、コンボボック、チェックボックスなど
多彩なコントロールが用意されていて入力処理がとても楽チンです。
なぜなら、それらコントロールによって、設定された値以外は返さないようになっているからです。

CUI環境だとそうはいきません。大文字・小文字の混在、typo、変なとこに空白が紛れていたり、うっかりENTERのみを押したりと色々なことをオペレーターさん達はやってくれます。
そして、このような誤操作に対しての再入力要求も用意しないといけません。

そんな中、必要に迫られて作ったのが、
gets_with_condition(
explanation = '', allow_only_enter = false, i_con = -> { gets(chomp: true) })

です。
gets_with_conditionの動作は、
カーソル位置を保存した後、i_conを.callして文字列の入力を受け付けます。
その後、ブロック内で設定された、true/falseの通過条件を確認し、
条件を満たさなければ、カーソル位置を復帰させ、画面の誤入力を抹消し、
ループの初めに戻ります。

module CommonModule

private

 # following to need to put a line for using ANSI Escape Sequence
 # require_relative '該当するディレクトリ/ansi_escape_sequence'
 def gets_with_condition(explanation = '', allow_only_enter = false, i_con = -> { gets(chomp: true) })
   print explanation unless explanation.empty?                           # not show explanation if explanation = ''
   print ESC.save                                                        # Save cursor position
   return_value = nil
   loop do
     return_value = i_con.call                                           # get return value
     if allow_only_enter && return_value.empty?
       break
     end                                                                 # in case of allow_only_enter = true, break by only enter

     passing_condition = yield(return_value)                             # process return_value inside the block
     passing_condition ? break : (print ESC.undo, ESC.del(0))            # true => break, false => retry
   end
   return_value                                                          # *** NOTE: the return value doesn't change in the block ***
                                                                         #             watch the line of 'return_value = i_con.call'
 end
end

ESC.save, ESC.undo, ESC.delを使っているので、前回の記事のANSI::EscapeSequence
は必須です。

また、モジュールのメソッドの呼び出しの引数の中に謎の古代文字
i_con = -> { gets(chomp: true) }
が入っていますが、ここは普段はイジりません。応用的な使い方をする際に活躍します。記憶の片隅にでも仕舞っておいてください。

以前の例題にgets_with_conditionを適用すると、

この中にあるMyClassクラスは、下のようになります。

ESC = ANSI::EscapeSequence       # ここに必要モジュールを追加 

class MyClass
  include CommonModule           # ここに必要モジュールを追加 
  
  def initialize
    @asking         = 'あなたは男性ですか?女性ですか?(man/woman)'
    @condition      = ['man', 'woman']
    @reply_to_man   = 'さようなら。'
    @reply_to_woman = 'こんにちは。'
  end

  def greeting
    your_reply = ask.downcase    # gets_with_conditionの仕様が、ポンコツなので未加工のデータを返しているため。
    my_reply(your_reply)
  end

private

  def ask                        # ここをgets_with_conditionで書き換え
    gets_with_condition("#{@asking} ==> ") do |reply|
      @condition.include?(reply.downcase)
    end
  end

  def my_reply(your_reply)
    if your_reply == 'woman'
      print "#{@reply_to_woman}\n"
    elsif your_reply == 'man'   # womanとmanの2値しか無いので、elseが不要
      print "#{@reply_to_man}\n"
    end
  end
end

因みに、.greetingの同値テストは、こんな感じです。
1つのテストメソッドで、6つの検証をこなしていることが分かります。

require 'minitest/autorun'
class MyClassTest < MiniTest::Test
 include Tools                   # ここに必要モジュールを追加

 def setup
   @my_class = MyClass.new
 end

 def test_greeting
   asking         = 'あなたは男性ですか?女性ですか?(man/woman) ==> '
   reply_to_man   = "さようなら。\n"
   reply_to_woman = "こんにちは。\n"

   sequence       = [:sequence, 'man -> pass', 'Man -> pass', 'man -> fail, retry', 'woman -> pass', 'Woman -> pass', 'woman -> fail, retry']
   your_reply     = [:reply, "man\n", "Man\n", "men\nman\n", "woman\n", "Woman\n", "women\nwoman\n"]
   exp            = [
     :exp,
     asking + ESC.save + reply_to_man,
     asking + ESC.save + reply_to_man,
     asking + ESC.save + ESC.undo + ESC.del(0) + reply_to_man,
     asking + ESC.save + reply_to_woman,
     asking + ESC.save + reply_to_woman,
     asking + ESC.save + ESC.undo + ESC.del(0) + reply_to_woman,
   ]
   h_list = [sequence, your_reply, exp]
   h_list = make_hash_list_in_array(h_list)

   stdio_coverage(h_list) do |h, buf_in, buf_out|
     buf_in << h[:reply]
     @my_class.greeting
     assert_equal h[:exp],
                  buf_out,
                  "#{h[:sequence]}でテストに失敗しました。"
   end
 end
end

    ANSI::EscapeSequence
    .make_hash_list_in_array
    .stdio_coverage
    .gets_with_condition

記事の内容が全部盛りです。ふふ。

このテストでの注意点ですが、今回再入力の処理が加わってloopで回っています。失敗をさせて再入力の処理を検証するわけですが、必ず最後は成功させて下さい。
そうしないと、入力バッファが永久に入力待ちになって帰って来られません。
テストは壊れることによって、情報が得られるのですが、運が悪いとリセットするしかないのは、ちょっと困りものです。最悪、何が起こっているのかさえも分からない可能性があります。ポンコツ君が考え付くテストはこんな程度ですわ。何か良い指針がありましたら、教えて下さい。
まぁ、それでも、結構重宝してますけどね。

次からの投稿は、.gets_with_conditionの兄弟たちを紹介していく予定です。

この記事が気に入ったらサポートをしてみませんか?