Highline的使用

2018-03-06  本文已影响0人  wangzzzzz

前言

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()方法的执行流程可以简单的分为三步:

  1. 输出question
  2. 获取用户输入
  3. 返回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()方法是专门用在需要用户确认的场景。具体的流程是:

  1. 输出question
  2. 获取用户输入,且只接受y(es)或n(o)(忽略大小写)
  3. 返回值是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()的执行流程大体上没有改变,仍然是上面提到的那三个步骤:

  1. 输出带有列表的question
  2. 获取用户的选择
  3. 返回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)。

上一篇下一篇

猜你喜欢

热点阅读