Highline的使用

前言

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)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容