前言
HighLine是一个简化控制台输入输出的工具,它内部是使用像gets和puts这样低层次的方法来实现的。Highline提供了一个健壮的系统来获取用户的输入,并且对外提供了接口来对用户的输入进行错误检测和验证,最终会将输入的字符串转换成你想要的格式。
要想对一个系统进行深入了解,首先必须要非常熟悉他的使用方法。那么,Highline作为一款输入输出工具,它对外提供了四种常用的方法,分别是say、ask、agree和choose。(默认情况下,Highine都是与终端进行交互)
1. say
say()的作用和我们常用的puts非常相似,都是将文本输出到终端进行显示。那么say()到底有什么优势呢。
当我们需要向终端输入一段文本时,puts和say()分别是这样写的:
puts "ruby is good"
require "highline/import"
say "ruby is good"
二者几乎是一样的写法,但是使用say()还需要引入类库,好像是puts更胜一筹。
在作出判断之前,我们再来看一个需求,现在我们需要向终端输入一段带颜色的文本了,那么puts和say()分别是如何的写:
puts "\e[31mruby\e[0m is good"
require "highline/import"
say "<%=color('Ruby',:red) %> is good"
看到这里,可以说say()的优势非常明显了,使用puts输入带颜色的文字,我们不仅要记住\e[31m这样繁琐的写法,而且还要非常准确的记住red就是31m而不是32m、33m;而使用say()就没那么麻烦了,只需要记住一个:red或者一个:green就行了。
say()方法声明
#@param statement [Question, String] 最终会被显示在终端上
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) %>!")
在终端中执行这段代码
换行
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
执行结果
图中,wrap_at设置为71, 表示每一个行字符数最多为71,如果超过了,错过部分被截断后放到下一行,如果被截断处是一个单词(即没有包含空格),则这个单词整个放到下一行。如图中,在初始文本的第一行最后的"This tex"处是第71个字符,由于text是一个完整的单词,所以最终"text"整个被放到下一行了。(目前highline还不能准确的给带特殊格式(颜色、粗体等)的文本换行)
分页
require 'highline/import'
HighLine.default_instance.page_at = 2
say( (1..5).map { |n| "This is line #{n}.\n"}.join)
执行结果
2. ask
ask()的作用是在终端打印一个字符串,然后捕获用户的输入。
ask()方法声明
# @param template_or_question [String, Question],显示在终端中的question
# @param answer_type [Class],指出你想要返回的answer的类型
# @param details[Proc],设置Question相关的参数
def ask(template_or_question, answer_type = nil, &details)
一个简单的使用
require 'highline/import'
# Basic usage
answer = ask "What do you think?"
puts "You have answered: #{answer}"
执行结果
从图中可以看出,ask()方法的执行流程可以简单的分为三步:
- 输出question
- 获取用户输入
- 返回answer
在这三个步骤之间,Highline内部会执行许多操作,比如在第一步时,传给ask()方法的参数template_or_question可能是一个ERB模板字符串,则需要将其转换成常规的字符串;在第二步时,获取用户的输入之后,可能还需要进行验证,如验证失败,则需要重新输入;在第三步时,则需要根据参数answer_type,对用户的输入进行转换。
下面看一个例子,可以更好的表示这个流程
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}"
执行结果
在上述例子中,answer_type = Integer,所以在第一次输入ab之后,Highline会调用Kenel.Integer('ab'),很明显'ab'无法被转换成整数,故在传入'ab'之后,抛出异常,Highline截获异常之后,输出异常信息,然后等待用户重新输入;在上述例子中,在detail这个block中,设置了q.in = 0..105,表示只接受0到105这个范围的输入,故在输入'120'之后,需要重新输入。
以上两个例子只是展示了ask()方法的一小部分功能,如果要更深入的了解,可以下载源代码,执行一遍源代码中的例子,如果想要更全面的了解,可以看看源代码中的测试用例。
3. agree
agree()方法的作用是在终端打印一个字符串,然后等待用户输入y(es)或n(o)。
agree()方法是ask()方法的一个快捷方式,它内部最终会使用ask()方法来实现,agree()只是在ask()的基础上预先设置了一些参数,比如用户输入的验证规则和返回类型。
agree()方法声明
# @param yes_or_no_question [String],一个接受yes或no作为回答的问题
# @param character [Boolean, :getc, nil],决定用户输入方式,比如单字符输入或字符串输入
def agree(yes_or_no_question, character = nil)
agree()方法是专门用在需要用户确认的场景。具体的流程是:
- 输出question
- 获取用户输入,且只接受y(es)或n(o)(忽略大小写)
- 返回值是true 或 false
下面看一个简单的使用例子
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}"
执行结果
从上述例子可以看出,调用agree()非常简洁,而且由于在agree()内部预先设置了验证规则或返回类型,所以即使不传入别的参数,功能上也能满足我们。在上述例子中,因为 我们在block中设置了默认值,所以第二次运行这个例子时,直接按回车键,而没有输入任何字符,程序也能成功执行,最终返回的answer等于true(因为默认值等于'y')。
4. choose
choose()的作用是在终端打印一个可选列表,然后等待用户输入序号或内容进行选择。
choose()同样也是调用ask()来实现的,它内部会预先设置一些参数来控制如何将各个参数组装成question、如何验证用户的输入等
choose()方法声明
# @param items [Array<String>] 快速设置menu的items
# @param details [Proc] 进一步设置menu的属性
def choose(*items, &details)
同样的,我们通过一个简单的例子来快速了解一下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}"
执行结果
从上述列子中,我们可以看出,choose()的执行流程大体上没有改变,仍然是上面提到的那三个步骤:
- 输出带有列表的question
- 获取用户的选择
- 返回answer。
第一步同样是输出question,只是比之前多了一个可选列表,第二步则限制了用户只能输入列表中的选项或其对应的序列号。
对比agree(),我们发现choose()和agree()非常类似,它们都是在ask()的基础上进行了二次封装,它们都深入到了具体的使用场景中:agree()专门使用在需要用户确认的场景中,而choose则专门使用在需要用户选择的场景中。而ask()更加灵活,可以用于更多的场景,当然使用起来也更加繁琐。
以下是choose()的一个常用场景
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}"
在这个例子中,除了给menu设置header
,还增加了prompt
,它们分别被打印在可选列表的前面和后面。对于序列号,进行了更细致的修改,增加了一个序列号的前缀")"
,并设置了序列号的颜色。
除此以外,还给列表中的每一个选项增加了一个响应block,就是说在选择了这个选项之后,会执行这个block。
执行结果
可以看出,question是由header、list、prompt从上到下组成的,可以通过设置这几个参数来自定义设置question。
在用户输入时,只能输入列表中的选项或其对应序列号,否则需要重新输入。
如果给选项添加了响应block,则这个block会先被保存起来,然后在用户输入且验证通过之后,这个block会被执行,执行的结果作为answer返回;如果没有传入block,则返回被选中选项的name作为answer(比如选中b,则返回orange)。