前言
HighLine是一個(gè)簡化控制臺(tái)輸入輸出的工具,它內(nèi)部是使用像gets和puts這樣低層次的方法來實(shí)現(xiàn)的。Highline提供了一個(gè)健壯的系統(tǒng)來獲取用戶的輸入,并且對外提供了接口來對用戶的輸入進(jìn)行錯(cuò)誤檢測和驗(yàn)證,最終會(huì)將輸入的字符串轉(zhuǎn)換成你想要的格式。
要想對一個(gè)系統(tǒng)進(jìn)行深入了解,首先必須要非常熟悉他的使用方法。那么,Highline作為一款輸入輸出工具,它對外提供了四種常用的方法,分別是say、ask、agree和choose。(默認(rèn)情況下,Highine都是與終端進(jìn)行交互)
1. say
say()的作用和我們常用的puts非常相似,都是將文本輸出到終端進(jìn)行顯示。那么say()到底有什么優(yōu)勢呢。
當(dāng)我們需要向終端輸入一段文本時(shí),puts和say()分別是這樣寫的:
puts "ruby is good"
require "highline/import"
say "ruby is good"
二者幾乎是一樣的寫法,但是使用say()還需要引入類庫,好像是puts更勝一籌。
在作出判斷之前,我們再來看一個(gè)需求,現(xiàn)在我們需要向終端輸入一段帶顏色的文本了,那么puts和say()分別是如何的寫:
puts "\e[31mruby\e[0m is good"
require "highline/import"
say "<%=color('Ruby',:red) %> is good"
看到這里,可以說say()的優(yōu)勢非常明顯了,使用puts輸入帶顏色的文字,我們不僅要記住\e[31m這樣繁瑣的寫法,而且還要非常準(zhǔn)確的記住red就是31m而不是32m、33m;而使用say()就沒那么麻煩了,只需要記住一個(gè):red或者一個(gè):green就行了。
say()方法聲明
#@param statement [Question, String] 最終會(huì)被顯示在終端上
def say(statement)
以下是say()方法常用的使用方式
require 'highline/import'
#一般的文本
say('This is general text!')
#帶顏色的文本
say("This should be <%= color('red', :red) %>!")
#帶背景色的文本
say("This should be <%= color('red on red', :on_red) %>!")
#帶粗體的文本
say("This should be <%= color('bold', :bold) %>!")
#帶下劃線的文本
say("This should be <%= color('underline', :underline) %>!")
#帶閃爍的文本
say("This should be <%= color('blink', :blink) %>!")
在終端中執(zhí)行這段代碼

換行
require "highline/import"
line = "This is a long flowing paragraph meant to span " \
"several lines. This text should definitely be " \
"wrapped at the set limit, in the result. Your code " \
"does well with things like this.\n\n"
puts "初始文本:"
puts line
puts "格式化后的文本:"
HighLine.default_instance.wrap_at = 71
say line
執(zhí)行結(jié)果

圖中,wrap_at設(shè)置為71, 表示每一個(gè)行字符數(shù)最多為71,如果超過了,錯(cuò)過部分被截?cái)嗪蠓诺较乱恍?,如果被截?cái)嗵幨且粋€(gè)單詞(即沒有包含空格),則這個(gè)單詞整個(gè)放到下一行。如圖中,在初始文本的第一行最后的"This tex"處是第71個(gè)字符,由于text是一個(gè)完整的單詞,所以最終"text"整個(gè)被放到下一行了。(目前highline還不能準(zhǔn)確的給帶特殊格式(顏色、粗體等)的文本換行)
分頁
require 'highline/import'
HighLine.default_instance.page_at = 2
say( (1..5).map { |n| "This is line #{n}.\n"}.join)
執(zhí)行結(jié)果

2. ask
ask()的作用是在終端打印一個(gè)字符串,然后捕獲用戶的輸入。
ask()方法聲明
# @param template_or_question [String, Question],顯示在終端中的question
# @param answer_type [Class],指出你想要返回的answer的類型
# @param details[Proc],設(shè)置Question相關(guān)的參數(shù)
def ask(template_or_question, answer_type = nil, &details)
一個(gè)簡單的使用
require 'highline/import'
# Basic usage
answer = ask "What do you think?"
puts "You have answered: #{answer}"
執(zhí)行結(jié)果

從圖中可以看出,ask()方法的執(zhí)行流程可以簡單的分為三步:
- 輸出question
- 獲取用戶輸入
- 返回answer
在這三個(gè)步驟之間,Highline內(nèi)部會(huì)執(zhí)行許多操作,比如在第一步時(shí),傳給ask()方法的參數(shù)template_or_question可能是一個(gè)ERB模板字符串,則需要將其轉(zhuǎn)換成常規(guī)的字符串;在第二步時(shí),獲取用戶的輸入之后,可能還需要進(jìn)行驗(yàn)證,如驗(yàn)證失敗,則需要重新輸入;在第三步時(shí),則需要根據(jù)參數(shù)answer_type,對用戶的輸入進(jìn)行轉(zhuǎn)換。
下面看一個(gè)例子,可以更好的表示這個(gè)流程
require 'highline/import'
answer = ask("Age? ", Integer) { |q| q.in = 0..105 }
puts "your answer is #{answer}"
puts "your answer's class is #{answer.class}"
執(zhí)行結(jié)果

在上述例子中,answer_type = Integer,所以在第一次輸入ab之后,Highline會(huì)調(diào)用Kenel.Integer('ab'),很明顯'ab'無法被轉(zhuǎn)換成整數(shù),故在傳入'ab'之后,拋出異常,Highline截獲異常之后,輸出異常信息,然后等待用戶重新輸入;在上述例子中,在detail這個(gè)block中,設(shè)置了q.in = 0..105,表示只接受0到105這個(gè)范圍的輸入,故在輸入'120'之后,需要重新輸入。
以上兩個(gè)例子只是展示了ask()方法的一小部分功能,如果要更深入的了解,可以下載源代碼,執(zhí)行一遍源代碼中的例子,如果想要更全面的了解,可以看看源代碼中的測試用例。
3. agree
agree()方法的作用是在終端打印一個(gè)字符串,然后等待用戶輸入y(es)或n(o)。
agree()方法是ask()方法的一個(gè)快捷方式,它內(nèi)部最終會(huì)使用ask()方法來實(shí)現(xiàn),agree()只是在ask()的基礎(chǔ)上預(yù)先設(shè)置了一些參數(shù),比如用戶輸入的驗(yàn)證規(guī)則和返回類型。
agree()方法聲明
# @param yes_or_no_question [String],一個(gè)接受yes或no作為回答的問題
# @param character [Boolean, :getc, nil],決定用戶輸入方式,比如單字符輸入或字符串輸入
def agree(yes_or_no_question, character = nil)
agree()方法是專門用在需要用戶確認(rèn)的場景。具體的流程是:
- 輸出question
- 獲取用戶輸入,且只接受y(es)或n(o)(忽略大小寫)
- 返回值是true 或 false
下面看一個(gè)簡單的使用例子
require 'highline/import'
answer = agree("Yes or no?"){ |q| q.default = "y" }
puts "your answer is #{answer}"
puts "your answer's class is #{answer.class}"
執(zhí)行結(jié)果

從上述例子可以看出,調(diào)用agree()非常簡潔,而且由于在agree()內(nèi)部預(yù)先設(shè)置了驗(yàn)證規(guī)則或返回類型,所以即使不傳入別的參數(shù),功能上也能滿足我們。在上述例子中,因?yàn)?我們在block中設(shè)置了默認(rèn)值,所以第二次運(yùn)行這個(gè)例子時(shí),直接按回車鍵,而沒有輸入任何字符,程序也能成功執(zhí)行,最終返回的answer等于true(因?yàn)槟J(rèn)值等于'y')。
4. choose
choose()的作用是在終端打印一個(gè)可選列表,然后等待用戶輸入序號或內(nèi)容進(jìn)行選擇。
choose()同樣也是調(diào)用ask()來實(shí)現(xiàn)的,它內(nèi)部會(huì)預(yù)先設(shè)置一些參數(shù)來控制如何將各個(gè)參數(shù)組裝成question、如何驗(yàn)證用戶的輸入等
choose()方法聲明
# @param items [Array<String>] 快速設(shè)置menu的items
# @param details [Proc] 進(jìn)一步設(shè)置menu的屬性
def choose(*items, &details)
同樣的,我們通過一個(gè)簡單的例子來快速了解一下choose()的功能
require 'highline/import'
answer = choose("apple", "orange", "pear", "banana") do |menu|
menu.header = "which fruit do you like"
end
puts "your answer is #{answer}"
執(zhí)行結(jié)果

從上述列子中,我們可以看出,choose()的執(zhí)行流程大體上沒有改變,仍然是上面提到的那三個(gè)步驟:
- 輸出帶有列表的question
- 獲取用戶的選擇
- 返回answer。
第一步同樣是輸出question,只是比之前多了一個(gè)可選列表,第二步則限制了用戶只能輸入列表中的選項(xiàng)或其對應(yīng)的序列號。
對比agree(),我們發(fā)現(xiàn)choose()和agree()非常類似,它們都是在ask()的基礎(chǔ)上進(jìn)行了二次封裝,它們都深入到了具體的使用場景中:agree()專門使用在需要用戶確認(rèn)的場景中,而choose則專門使用在需要用戶選擇的場景中。而ask()更加靈活,可以用于更多的場景,當(dāng)然使用起來也更加繁瑣。
以下是choose()的一個(gè)常用場景
require 'highline/import'
answer = choose do |menu|
menu.header = "There are some fruit"
menu.prompt = "What's your favourite fruit?"
menu.index = :letter
menu.index_suffix = ") "
menu.index_color = :red
menu.choice(:apple) do |answer|
say("Good choice!")
answer
end
menu.choices(:orange, :pear) do |answer|
say("Bad choice!")
answer
end
end
puts "your answer is #{answer}"
在這個(gè)例子中,除了給menu設(shè)置header,還增加了prompt,它們分別被打印在可選列表的前面和后面。對于序列號,進(jìn)行了更細(xì)致的修改,增加了一個(gè)序列號的前綴")",并設(shè)置了序列號的顏色。
除此以外,還給列表中的每一個(gè)選項(xiàng)增加了一個(gè)響應(yīng)block,就是說在選擇了這個(gè)選項(xiàng)之后,會(huì)執(zhí)行這個(gè)block。
執(zhí)行結(jié)果

可以看出,question是由header、list、prompt從上到下組成的,可以通過設(shè)置這幾個(gè)參數(shù)來自定義設(shè)置question。
在用戶輸入時(shí),只能輸入列表中的選項(xiàng)或其對應(yīng)序列號,否則需要重新輸入。
如果給選項(xiàng)添加了響應(yīng)block,則這個(gè)block會(huì)先被保存起來,然后在用戶輸入且驗(yàn)證通過之后,這個(gè)block會(huì)被執(zhí)行,執(zhí)行的結(jié)果作為answer返回;如果沒有傳入block,則返回被選中選項(xiàng)的name作為answer(比如選中b,則返回orange)。