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の兄弟たちを紹介していく予定です。